{"repo": "baaaaaaaka/codex-helper", "files": {"internal/cli/teams_service_test.go": {"acd9e9f5f436b95be8335d1a1aa0e32eb0be067b": {"t": 1785745654.121891, "n": 289435, "h": "505a88a478d08268", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperpath\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperruntime\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc TestPreferredCXPServiceEntryDoesNotUseUnrelatedRuntime(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\truntimeDir := filepath.Join(tmp, \"go-bin\")\n\tmanagedDir := filepath.Join(tmp, \"managed-bin\")\n\tfor _, dir := range []string{runtimeDir, managedDir} {\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcurrentEntry := filepath.Join(runtimeDir, helperruntime.BinaryName(runtime.GOOS))\n\tmanagedPath := filepath.Join(managedDir, helperpath.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, currentEntry, \"runtime\", 0o755)\n\twriteCLIFile(t, managedPath, \"managed\", 0o755)\n\troot := filepath.Join(runtimeDir, \".cxp-runtime\")\n\tif err := helperruntime.Activate(root, \"v1.2.3\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, root)\n\tt.Setenv(helperruntime.EnvEntryPath, currentEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: runtime.GOOS, stat: serviceExecutableStatForTest})\n\n\tif got := preferredCXPServiceEntry(managedPath); got != \"\" {\n\t\tt.Fatalf(\"unrelated runtime entry leaked into managed service spec: %q\", got)\n\t}\n\tmanagedCXP := filepath.Join(managedDir, helperruntime.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, managedCXP, \"stable\", 0o755)\n\tif got := preferredCXPServiceEntry(managedPath); got != managedCXP {\n\t\tt.Fatalf(\"preferred entry = %q, want managed stable entry %q\", got, managedCXP)\n\t}\n}\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \".codex\"))\n\tt.Setenv(\"CODEX_DIR\", filepath.Join(tmp, \".codex\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + systemdQuoteArg(stableHome),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --managed-service-child --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tif cfg.Spec.WorkingDir != stableHome {\n\t\tt.Fatalf(\"config working dir = %q, want stable home %q\", cfg.Spec.WorkingDir, stableHome)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stableCXP) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesCurrentImmutableRuntimeInsteadOfOldLauncher(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tgoBinDir := filepath.Join(tmp, \"go\", \"bin\")\n\tlauncher := filepath.Join(goBinDir, \"codex-proxy\")\n\tentry := filepath.Join(goBinDir, \"cxp\")\n\truntimeRoot := filepath.Join(goBinDir, \".cxp-runtime\")\n\timmutableRuntime := helperruntime.VersionPath(runtimeRoot, \"v1.2.4\", runtime.GOOS)\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, entry, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, immutableRuntime, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := helperruntime.Activate(runtimeRoot, \"v1.2.4\"); err != nil {\n\t\tt.Fatalf(\"activate immutable runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, runtimeRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v1.2.4\")\n\tt.Setenv(helperruntime.EnvEntryPath, entry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: immutableRuntime,\n\t\targv0: launcher,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was copied from the old stable launcher instead of the immutable runtime: %s\", managedVersion)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want immutable runtime version at %s\", record, managed)\n\t}\n}\n\nfunc TestManagedTeamsMaterializationIgnoresUnrelatedRuntimeEnvironment(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tlauncher := filepath.Join(tmp, \"fixture\", \"codex-proxy\")\n\tunrelatedEntry := filepath.Join(tmp, \"live\", \"cxp\")\n\tunrelatedRoot := filepath.Join(tmp, \"live\", \".cxp-runtime\")\n\tunrelatedRuntime := helperruntime.VersionPath(unrelatedRoot, \"v9.9.9\", runtime.GOOS)\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, unrelatedEntry, \"9.9.9\")\n\twriteVersionedHelperForServiceTest(t, unrelatedRuntime, \"9.9.9\")\n\tif err := helperruntime.Activate(unrelatedRoot, \"v9.9.9\"); err != nil {\n\t\tt.Fatalf(\"activate unrelated runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, unrelatedRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v9.9.9\")\n\tt.Setenv(helperruntime.EnvEntryPath, unrelatedEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: launcher,\n\t\targv0: launcher,\n\t\tstat: serviceExecutableStatForTest,\n\t})\n\n\tif got := managedTeamsMaterializationSource(launcher, launcher); got != launcher {\n\t\tt.Fatalf(\"materialization source = %q, want unrelated runtime environment ignored in favor of %q\", got, launcher)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallReplacesManagedDefaultSymlinkToGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.MkdirAll(filepath.Dir(managed), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed dir: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, managed); err != nil {\n\t\tt.Fatalf(\"symlink managed to go/bin: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tif info, err := os.Lstat(managed); err != nil {\n\t\tt.Fatalf(\"lstat managed helper: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(managed)\n\t\tt.Fatalf(\"managed helper should be materialized as a real file, still symlink to %q\", target)\n\t}\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired stable cxp entry, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tif err := os.MkdirAll(home, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir effective home: %v\", err)\n\t}\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallFailsClosedWhenStableHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), \"resolve stable Teams service home\") {\n\t\tt.Fatalf(\"service install error = %v, want stable-home resolution failure\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"failed service install should not write a unit, stat error = %v\", err)\n\t}\n}\n\nfunc TestTeamsRuntimeSafetyServiceEnvironmentDoesNotPersistWSLDynamicLoopbackProxyByDefaultCI(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\targv0: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s persisted dynamic WSL loopback proxy %q in the background service: %#v\", name, value, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentOmitsRemovedStaticProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"MIMO_API_KEY\", \"DEEPSEEK_API_KEY\"} {\n\t\tif env[name] != \"\" {\n\t\t\tt.Fatalf(\"removed static provider key %s should not be preserved for the Teams service: %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConfiguredExternalProviderEnvKeys(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigPath := filepath.Join(tmp, \"config.json\")\n\traw := `{\"version\":6,\"profiles\":[],\"modelConfigVersion\":1,\"modelCredentials\":{\"nvidia\":{\"apiKeyRef\":\"env:NVIDIA_API_KEY\"}},\"modelProfiles\":{\"nvidia/deepseek-v4\":{\"provider\":\"nvidia\",\"model\":\"nvidia/deepseek-v4\",\"apiKeyRef\":\"env:NVIDIA_API_KEY\",\"revision\":1}}}`\n\tif err := os.WriteFile(configPath, []byte(raw), 0o600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", configPath)\n\tt.Setenv(\"NVIDIA_API_KEY\", \"sk-nvidia-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tenv := teamsServiceEnvironment()\n\tif env[\"NVIDIA_API_KEY\"] != \"sk-nvidia-test\" {\n\t\tt.Fatalf(\"configured external provider env key was not preserved: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unrelated model key leaked into service environment: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --managed-service-child --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, removed := range []string{\"DEEPSEEK_API_KEY\", \"MIMO_API_KEY\"} {\n\t\tif strings.Contains(unit, removed) {\n\t\t\tt.Fatalf(\"unit preserved removed static provider key %s:\\n%s\", removed, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--managed-service-child\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + stableHome + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--managed-service-child' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tif err := os.MkdirAll(home, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir effective home: %v\", err)\n\t}\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceDoctorUsesInstalledWSLStartupFallbackWithoutScheduledTaskRequirement(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tif err := os.WriteFile(markerPath, []byte(buildTeamsServiceWSLStartupFallbackConfig(backend.Name(), []string{\"-d\", \"Ubuntu\", \"--\", \"true\"})), 0o600); err != nil {\n\t\tt.Fatalf(\"write fallback marker: %v\", err)\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute fallback doctor: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service preferred backend: wsl-windows-task-scheduler\",\n\t\t\"Teams service effective backend: wsl-startup-watchdog\",\n\t\t\"WSL Startup watchdog fallback: ready\",\n\t\t\"ScheduledTask cmdlets are not required\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"fallback doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"fallback readiness calls = %#v, want wsl.exe check plus optional task probe\", runner.calls)\n\t}\n\tfirst := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(first, \"Get-Command wsl.exe\") || strings.Contains(first, \"Get-ScheduledTask -ErrorAction Stop\") {\n\t\tt.Fatalf(\"effective fallback readiness incorrectly requires ScheduledTask cmdlets: %s\", first)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLStartupFallbackDoctorRejectsMismatchedMarker(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tmarker string\n\t\twant string\n\t}{\n\t\t{name: \"wrong task\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=other-task\\nCommand=wsl.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want \\\"\" + backend.Name() + \"\\\"\"},\n\t\t{name: \"wrong command\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=powershell.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want wsl.exe\"},\n\t\t{name: \"invalid arguments\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=wsl.exe\\nArguments=\\\"unterminated\\n\", want: \"invalid Arguments\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif err := os.WriteFile(markerPath, []byte(tc.marker), 0o600); err != nil {\n\t\t\t\tt.Fatalf(\"write marker: %v\", err)\n\t\t\t}\n\t\t\terr := runTeamsServiceWSLStartupFallbackReadinessCheck(context.Background(), io.Discard, backend)\n\t\t\tif err == nil || !strings.Contains(err.Error(), tc.want) {\n\t\t\t\tt.Fatalf(\"readiness error = %v, want %q\", err, tc.want)\n\t\t\t}\n\t\t})\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"invalid markers should fail before PowerShell probes: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, os.Getenv(\"HOME\"))\n\twantInvocationDir := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantInvocationDir, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --managed-service-child --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslInteropAvailable *bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLInteropAvailable := teamsServiceWSLInteropAvailable\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tif hooks.wslInteropAvailable != nil {\n\t\tteamsServiceWSLInteropAvailable = func() bool { return *hooks.wslInteropAvailable }\n\t} else if hooks.isWSL {\n\t\t// Unit tests that explicitly simulate WSL should not inherit the host\n\t\t// runner's binfmt state. Tests for unavailable interop opt out via the\n\t\t// explicit hook above.\n\t\tteamsServiceWSLInteropAvailable = func() bool { return true }\n\t}\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLInteropAvailable = prevWSLInteropAvailable\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "83cd5ff9b9cf272755bd802c34c70d474f97e969": {"t": 1785755419.5425787, "n": 289435, "h": "505a88a478d08268", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperpath\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperruntime\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc TestPreferredCXPServiceEntryDoesNotUseUnrelatedRuntime(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\truntimeDir := filepath.Join(tmp, \"go-bin\")\n\tmanagedDir := filepath.Join(tmp, \"managed-bin\")\n\tfor _, dir := range []string{runtimeDir, managedDir} {\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcurrentEntry := filepath.Join(runtimeDir, helperruntime.BinaryName(runtime.GOOS))\n\tmanagedPath := filepath.Join(managedDir, helperpath.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, currentEntry, \"runtime\", 0o755)\n\twriteCLIFile(t, managedPath, \"managed\", 0o755)\n\troot := filepath.Join(runtimeDir, \".cxp-runtime\")\n\tif err := helperruntime.Activate(root, \"v1.2.3\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, root)\n\tt.Setenv(helperruntime.EnvEntryPath, currentEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: runtime.GOOS, stat: serviceExecutableStatForTest})\n\n\tif got := preferredCXPServiceEntry(managedPath); got != \"\" {\n\t\tt.Fatalf(\"unrelated runtime entry leaked into managed service spec: %q\", got)\n\t}\n\tmanagedCXP := filepath.Join(managedDir, helperruntime.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, managedCXP, \"stable\", 0o755)\n\tif got := preferredCXPServiceEntry(managedPath); got != managedCXP {\n\t\tt.Fatalf(\"preferred entry = %q, want managed stable entry %q\", got, managedCXP)\n\t}\n}\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \".codex\"))\n\tt.Setenv(\"CODEX_DIR\", filepath.Join(tmp, \".codex\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + systemdQuoteArg(stableHome),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --managed-service-child --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tif cfg.Spec.WorkingDir != stableHome {\n\t\tt.Fatalf(\"config working dir = %q, want stable home %q\", cfg.Spec.WorkingDir, stableHome)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stableCXP) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesCurrentImmutableRuntimeInsteadOfOldLauncher(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tgoBinDir := filepath.Join(tmp, \"go\", \"bin\")\n\tlauncher := filepath.Join(goBinDir, \"codex-proxy\")\n\tentry := filepath.Join(goBinDir, \"cxp\")\n\truntimeRoot := filepath.Join(goBinDir, \".cxp-runtime\")\n\timmutableRuntime := helperruntime.VersionPath(runtimeRoot, \"v1.2.4\", runtime.GOOS)\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, entry, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, immutableRuntime, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := helperruntime.Activate(runtimeRoot, \"v1.2.4\"); err != nil {\n\t\tt.Fatalf(\"activate immutable runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, runtimeRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v1.2.4\")\n\tt.Setenv(helperruntime.EnvEntryPath, entry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: immutableRuntime,\n\t\targv0: launcher,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was copied from the old stable launcher instead of the immutable runtime: %s\", managedVersion)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want immutable runtime version at %s\", record, managed)\n\t}\n}\n\nfunc TestManagedTeamsMaterializationIgnoresUnrelatedRuntimeEnvironment(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tlauncher := filepath.Join(tmp, \"fixture\", \"codex-proxy\")\n\tunrelatedEntry := filepath.Join(tmp, \"live\", \"cxp\")\n\tunrelatedRoot := filepath.Join(tmp, \"live\", \".cxp-runtime\")\n\tunrelatedRuntime := helperruntime.VersionPath(unrelatedRoot, \"v9.9.9\", runtime.GOOS)\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, unrelatedEntry, \"9.9.9\")\n\twriteVersionedHelperForServiceTest(t, unrelatedRuntime, \"9.9.9\")\n\tif err := helperruntime.Activate(unrelatedRoot, \"v9.9.9\"); err != nil {\n\t\tt.Fatalf(\"activate unrelated runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, unrelatedRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v9.9.9\")\n\tt.Setenv(helperruntime.EnvEntryPath, unrelatedEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: launcher,\n\t\targv0: launcher,\n\t\tstat: serviceExecutableStatForTest,\n\t})\n\n\tif got := managedTeamsMaterializationSource(launcher, launcher); got != launcher {\n\t\tt.Fatalf(\"materialization source = %q, want unrelated runtime environment ignored in favor of %q\", got, launcher)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallReplacesManagedDefaultSymlinkToGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.MkdirAll(filepath.Dir(managed), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed dir: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, managed); err != nil {\n\t\tt.Fatalf(\"symlink managed to go/bin: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tif info, err := os.Lstat(managed); err != nil {\n\t\tt.Fatalf(\"lstat managed helper: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(managed)\n\t\tt.Fatalf(\"managed helper should be materialized as a real file, still symlink to %q\", target)\n\t}\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired stable cxp entry, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tif err := os.MkdirAll(home, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir effective home: %v\", err)\n\t}\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallFailsClosedWhenStableHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), \"resolve stable Teams service home\") {\n\t\tt.Fatalf(\"service install error = %v, want stable-home resolution failure\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"failed service install should not write a unit, stat error = %v\", err)\n\t}\n}\n\nfunc TestTeamsRuntimeSafetyServiceEnvironmentDoesNotPersistWSLDynamicLoopbackProxyByDefaultCI(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\targv0: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s persisted dynamic WSL loopback proxy %q in the background service: %#v\", name, value, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentOmitsRemovedStaticProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"MIMO_API_KEY\", \"DEEPSEEK_API_KEY\"} {\n\t\tif env[name] != \"\" {\n\t\t\tt.Fatalf(\"removed static provider key %s should not be preserved for the Teams service: %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConfiguredExternalProviderEnvKeys(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigPath := filepath.Join(tmp, \"config.json\")\n\traw := `{\"version\":6,\"profiles\":[],\"modelConfigVersion\":1,\"modelCredentials\":{\"nvidia\":{\"apiKeyRef\":\"env:NVIDIA_API_KEY\"}},\"modelProfiles\":{\"nvidia/deepseek-v4\":{\"provider\":\"nvidia\",\"model\":\"nvidia/deepseek-v4\",\"apiKeyRef\":\"env:NVIDIA_API_KEY\",\"revision\":1}}}`\n\tif err := os.WriteFile(configPath, []byte(raw), 0o600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", configPath)\n\tt.Setenv(\"NVIDIA_API_KEY\", \"sk-nvidia-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tenv := teamsServiceEnvironment()\n\tif env[\"NVIDIA_API_KEY\"] != \"sk-nvidia-test\" {\n\t\tt.Fatalf(\"configured external provider env key was not preserved: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unrelated model key leaked into service environment: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --managed-service-child --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, removed := range []string{\"DEEPSEEK_API_KEY\", \"MIMO_API_KEY\"} {\n\t\tif strings.Contains(unit, removed) {\n\t\t\tt.Fatalf(\"unit preserved removed static provider key %s:\\n%s\", removed, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--managed-service-child\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + stableHome + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--managed-service-child' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tif err := os.MkdirAll(home, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir effective home: %v\", err)\n\t}\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceDoctorUsesInstalledWSLStartupFallbackWithoutScheduledTaskRequirement(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tif err := os.WriteFile(markerPath, []byte(buildTeamsServiceWSLStartupFallbackConfig(backend.Name(), []string{\"-d\", \"Ubuntu\", \"--\", \"true\"})), 0o600); err != nil {\n\t\tt.Fatalf(\"write fallback marker: %v\", err)\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute fallback doctor: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service preferred backend: wsl-windows-task-scheduler\",\n\t\t\"Teams service effective backend: wsl-startup-watchdog\",\n\t\t\"WSL Startup watchdog fallback: ready\",\n\t\t\"ScheduledTask cmdlets are not required\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"fallback doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"fallback readiness calls = %#v, want wsl.exe check plus optional task probe\", runner.calls)\n\t}\n\tfirst := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(first, \"Get-Command wsl.exe\") || strings.Contains(first, \"Get-ScheduledTask -ErrorAction Stop\") {\n\t\tt.Fatalf(\"effective fallback readiness incorrectly requires ScheduledTask cmdlets: %s\", first)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLStartupFallbackDoctorRejectsMismatchedMarker(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tmarker string\n\t\twant string\n\t}{\n\t\t{name: \"wrong task\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=other-task\\nCommand=wsl.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want \\\"\" + backend.Name() + \"\\\"\"},\n\t\t{name: \"wrong command\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=powershell.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want wsl.exe\"},\n\t\t{name: \"invalid arguments\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=wsl.exe\\nArguments=\\\"unterminated\\n\", want: \"invalid Arguments\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif err := os.WriteFile(markerPath, []byte(tc.marker), 0o600); err != nil {\n\t\t\t\tt.Fatalf(\"write marker: %v\", err)\n\t\t\t}\n\t\t\terr := runTeamsServiceWSLStartupFallbackReadinessCheck(context.Background(), io.Discard, backend)\n\t\t\tif err == nil || !strings.Contains(err.Error(), tc.want) {\n\t\t\t\tt.Fatalf(\"readiness error = %v, want %q\", err, tc.want)\n\t\t\t}\n\t\t})\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"invalid markers should fail before PowerShell probes: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, os.Getenv(\"HOME\"))\n\twantInvocationDir := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantInvocationDir, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --managed-service-child --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslInteropAvailable *bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLInteropAvailable := teamsServiceWSLInteropAvailable\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tif hooks.wslInteropAvailable != nil {\n\t\tteamsServiceWSLInteropAvailable = func() bool { return *hooks.wslInteropAvailable }\n\t} else if hooks.isWSL {\n\t\t// Unit tests that explicitly simulate WSL should not inherit the host\n\t\t// runner's binfmt state. Tests for unavailable interop opt out via the\n\t\t// explicit hook above.\n\t\tteamsServiceWSLInteropAvailable = func() bool { return true }\n\t}\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLInteropAvailable = prevWSLInteropAvailable\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "922278bcaeec54cdfe97476fb372b8cbd4563dbd": {"t": 1785755421.5918028, "n": 289184, "h": "f622c171cb3176bf", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperpath\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperruntime\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc TestPreferredCXPServiceEntryDoesNotUseUnrelatedRuntime(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\truntimeDir := filepath.Join(tmp, \"go-bin\")\n\tmanagedDir := filepath.Join(tmp, \"managed-bin\")\n\tfor _, dir := range []string{runtimeDir, managedDir} {\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcurrentEntry := filepath.Join(runtimeDir, helperruntime.BinaryName(runtime.GOOS))\n\tmanagedPath := filepath.Join(managedDir, helperpath.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, currentEntry, \"runtime\", 0o755)\n\twriteCLIFile(t, managedPath, \"managed\", 0o755)\n\troot := filepath.Join(runtimeDir, \".cxp-runtime\")\n\tif err := helperruntime.Activate(root, \"v1.2.3\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, root)\n\tt.Setenv(helperruntime.EnvEntryPath, currentEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: runtime.GOOS, stat: serviceExecutableStatForTest})\n\n\tif got := preferredCXPServiceEntry(managedPath); got != \"\" {\n\t\tt.Fatalf(\"unrelated runtime entry leaked into managed service spec: %q\", got)\n\t}\n\tmanagedCXP := filepath.Join(managedDir, helperruntime.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, managedCXP, \"stable\", 0o755)\n\tif got := preferredCXPServiceEntry(managedPath); got != managedCXP {\n\t\tt.Fatalf(\"preferred entry = %q, want managed stable entry %q\", got, managedCXP)\n\t}\n}\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + systemdQuoteArg(stableHome),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tif cfg.Spec.WorkingDir != stableHome {\n\t\tt.Fatalf(\"config working dir = %q, want stable home %q\", cfg.Spec.WorkingDir, stableHome)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stableCXP) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesCurrentImmutableRuntimeInsteadOfOldLauncher(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tgoBinDir := filepath.Join(tmp, \"go\", \"bin\")\n\tlauncher := filepath.Join(goBinDir, \"codex-proxy\")\n\tentry := filepath.Join(goBinDir, \"cxp\")\n\truntimeRoot := filepath.Join(goBinDir, \".cxp-runtime\")\n\timmutableRuntime := helperruntime.VersionPath(runtimeRoot, \"v1.2.4\", runtime.GOOS)\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, entry, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, immutableRuntime, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := helperruntime.Activate(runtimeRoot, \"v1.2.4\"); err != nil {\n\t\tt.Fatalf(\"activate immutable runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, runtimeRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v1.2.4\")\n\tt.Setenv(helperruntime.EnvEntryPath, entry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: immutableRuntime,\n\t\targv0: launcher,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was copied from the old stable launcher instead of the immutable runtime: %s\", managedVersion)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want immutable runtime version at %s\", record, managed)\n\t}\n}\n\nfunc TestManagedTeamsMaterializationIgnoresUnrelatedRuntimeEnvironment(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tlauncher := filepath.Join(tmp, \"fixture\", \"codex-proxy\")\n\tunrelatedEntry := filepath.Join(tmp, \"live\", \"cxp\")\n\tunrelatedRoot := filepath.Join(tmp, \"live\", \".cxp-runtime\")\n\tunrelatedRuntime := helperruntime.VersionPath(unrelatedRoot, \"v9.9.9\", runtime.GOOS)\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, unrelatedEntry, \"9.9.9\")\n\twriteVersionedHelperForServiceTest(t, unrelatedRuntime, \"9.9.9\")\n\tif err := helperruntime.Activate(unrelatedRoot, \"v9.9.9\"); err != nil {\n\t\tt.Fatalf(\"activate unrelated runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, unrelatedRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v9.9.9\")\n\tt.Setenv(helperruntime.EnvEntryPath, unrelatedEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: launcher,\n\t\targv0: launcher,\n\t\tstat: serviceExecutableStatForTest,\n\t})\n\n\tif got := managedTeamsMaterializationSource(launcher, launcher); got != launcher {\n\t\tt.Fatalf(\"materialization source = %q, want unrelated runtime environment ignored in favor of %q\", got, launcher)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallReplacesManagedDefaultSymlinkToGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.MkdirAll(filepath.Dir(managed), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed dir: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, managed); err != nil {\n\t\tt.Fatalf(\"symlink managed to go/bin: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tif info, err := os.Lstat(managed); err != nil {\n\t\tt.Fatalf(\"lstat managed helper: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(managed)\n\t\tt.Fatalf(\"managed helper should be materialized as a real file, still symlink to %q\", target)\n\t}\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired stable cxp entry, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tif err := os.MkdirAll(home, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir effective home: %v\", err)\n\t}\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallFailsClosedWhenStableHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), \"resolve stable Teams service home\") {\n\t\tt.Fatalf(\"service install error = %v, want stable-home resolution failure\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"failed service install should not write a unit, stat error = %v\", err)\n\t}\n}\n\nfunc TestTeamsRuntimeSafetyServiceEnvironmentDoesNotPersistWSLDynamicLoopbackProxyByDefaultCI(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\targv0: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s persisted dynamic WSL loopback proxy %q in the background service: %#v\", name, value, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentOmitsRemovedStaticProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"MIMO_API_KEY\", \"DEEPSEEK_API_KEY\"} {\n\t\tif env[name] != \"\" {\n\t\t\tt.Fatalf(\"removed static provider key %s should not be preserved for the Teams service: %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConfiguredExternalProviderEnvKeys(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigPath := filepath.Join(tmp, \"config.json\")\n\traw := `{\"version\":6,\"profiles\":[],\"modelConfigVersion\":1,\"modelCredentials\":{\"nvidia\":{\"apiKeyRef\":\"env:NVIDIA_API_KEY\"}},\"modelProfiles\":{\"nvidia/deepseek-v4\":{\"provider\":\"nvidia\",\"model\":\"nvidia/deepseek-v4\",\"apiKeyRef\":\"env:NVIDIA_API_KEY\",\"revision\":1}}}`\n\tif err := os.WriteFile(configPath, []byte(raw), 0o600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", configPath)\n\tt.Setenv(\"NVIDIA_API_KEY\", \"sk-nvidia-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tenv := teamsServiceEnvironment()\n\tif env[\"NVIDIA_API_KEY\"] != \"sk-nvidia-test\" {\n\t\tt.Fatalf(\"configured external provider env key was not preserved: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unrelated model key leaked into service environment: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, removed := range []string{\"DEEPSEEK_API_KEY\", \"MIMO_API_KEY\"} {\n\t\tif strings.Contains(unit, removed) {\n\t\t\tt.Fatalf(\"unit preserved removed static provider key %s:\\n%s\", removed, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + stableHome + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tif err := os.MkdirAll(home, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir effective home: %v\", err)\n\t}\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceDoctorUsesInstalledWSLStartupFallbackWithoutScheduledTaskRequirement(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tif err := os.WriteFile(markerPath, []byte(buildTeamsServiceWSLStartupFallbackConfig(backend.Name(), []string{\"-d\", \"Ubuntu\", \"--\", \"true\"})), 0o600); err != nil {\n\t\tt.Fatalf(\"write fallback marker: %v\", err)\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute fallback doctor: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service preferred backend: wsl-windows-task-scheduler\",\n\t\t\"Teams service effective backend: wsl-startup-watchdog\",\n\t\t\"WSL Startup watchdog fallback: ready\",\n\t\t\"ScheduledTask cmdlets are not required\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"fallback doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"fallback readiness calls = %#v, want wsl.exe check plus optional task probe\", runner.calls)\n\t}\n\tfirst := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(first, \"Get-Command wsl.exe\") || strings.Contains(first, \"Get-ScheduledTask -ErrorAction Stop\") {\n\t\tt.Fatalf(\"effective fallback readiness incorrectly requires ScheduledTask cmdlets: %s\", first)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLStartupFallbackDoctorRejectsMismatchedMarker(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tmarker string\n\t\twant string\n\t}{\n\t\t{name: \"wrong task\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=other-task\\nCommand=wsl.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want \\\"\" + backend.Name() + \"\\\"\"},\n\t\t{name: \"wrong command\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=powershell.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want wsl.exe\"},\n\t\t{name: \"invalid arguments\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=wsl.exe\\nArguments=\\\"unterminated\\n\", want: \"invalid Arguments\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif err := os.WriteFile(markerPath, []byte(tc.marker), 0o600); err != nil {\n\t\t\t\tt.Fatalf(\"write marker: %v\", err)\n\t\t\t}\n\t\t\terr := runTeamsServiceWSLStartupFallbackReadinessCheck(context.Background(), io.Discard, backend)\n\t\t\tif err == nil || !strings.Contains(err.Error(), tc.want) {\n\t\t\t\tt.Fatalf(\"readiness error = %v, want %q\", err, tc.want)\n\t\t\t}\n\t\t})\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"invalid markers should fail before PowerShell probes: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, os.Getenv(\"HOME\"))\n\twantInvocationDir := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantInvocationDir, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslInteropAvailable *bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLInteropAvailable := teamsServiceWSLInteropAvailable\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tif hooks.wslInteropAvailable != nil {\n\t\tteamsServiceWSLInteropAvailable = func() bool { return *hooks.wslInteropAvailable }\n\t} else if hooks.isWSL {\n\t\t// Unit tests that explicitly simulate WSL should not inherit the host\n\t\t// runner's binfmt state. Tests for unavailable interop opt out via the\n\t\t// explicit hook above.\n\t\tteamsServiceWSLInteropAvailable = func() bool { return true }\n\t}\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLInteropAvailable = prevWSLInteropAvailable\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "848d67b6636ffd6a127e0577ecf09b564543ca6f": {"t": 1785755423.7786834, "n": 289101, "h": "bae227102283a646", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperpath\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperruntime\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc TestPreferredCXPServiceEntryDoesNotUseUnrelatedRuntime(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\truntimeDir := filepath.Join(tmp, \"go-bin\")\n\tmanagedDir := filepath.Join(tmp, \"managed-bin\")\n\tfor _, dir := range []string{runtimeDir, managedDir} {\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcurrentEntry := filepath.Join(runtimeDir, helperruntime.BinaryName(runtime.GOOS))\n\tmanagedPath := filepath.Join(managedDir, helperpath.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, currentEntry, \"runtime\", 0o755)\n\twriteCLIFile(t, managedPath, \"managed\", 0o755)\n\troot := filepath.Join(runtimeDir, \".cxp-runtime\")\n\tif err := helperruntime.Activate(root, \"v1.2.3\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, root)\n\tt.Setenv(helperruntime.EnvEntryPath, currentEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: runtime.GOOS, stat: serviceExecutableStatForTest})\n\n\tif got := preferredCXPServiceEntry(managedPath); got != \"\" {\n\t\tt.Fatalf(\"unrelated runtime entry leaked into managed service spec: %q\", got)\n\t}\n\tmanagedCXP := filepath.Join(managedDir, helperruntime.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, managedCXP, \"stable\", 0o755)\n\tif got := preferredCXPServiceEntry(managedPath); got != managedCXP {\n\t\tt.Fatalf(\"preferred entry = %q, want managed stable entry %q\", got, managedCXP)\n\t}\n}\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + systemdQuoteArg(stableHome),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tif cfg.Spec.WorkingDir != stableHome {\n\t\tt.Fatalf(\"config working dir = %q, want stable home %q\", cfg.Spec.WorkingDir, stableHome)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stableCXP) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesCurrentImmutableRuntimeInsteadOfOldLauncher(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tgoBinDir := filepath.Join(tmp, \"go\", \"bin\")\n\tlauncher := filepath.Join(goBinDir, \"codex-proxy\")\n\tentry := filepath.Join(goBinDir, \"cxp\")\n\truntimeRoot := filepath.Join(goBinDir, \".cxp-runtime\")\n\timmutableRuntime := helperruntime.VersionPath(runtimeRoot, \"v1.2.4\", runtime.GOOS)\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, entry, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, immutableRuntime, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := helperruntime.Activate(runtimeRoot, \"v1.2.4\"); err != nil {\n\t\tt.Fatalf(\"activate immutable runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, runtimeRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v1.2.4\")\n\tt.Setenv(helperruntime.EnvEntryPath, entry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: immutableRuntime,\n\t\targv0: launcher,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was copied from the old stable launcher instead of the immutable runtime: %s\", managedVersion)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want immutable runtime version at %s\", record, managed)\n\t}\n}\n\nfunc TestManagedTeamsMaterializationIgnoresUnrelatedRuntimeEnvironment(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tlauncher := filepath.Join(tmp, \"fixture\", \"codex-proxy\")\n\tunrelatedEntry := filepath.Join(tmp, \"live\", \"cxp\")\n\tunrelatedRoot := filepath.Join(tmp, \"live\", \".cxp-runtime\")\n\tunrelatedRuntime := helperruntime.VersionPath(unrelatedRoot, \"v9.9.9\", runtime.GOOS)\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, unrelatedEntry, \"9.9.9\")\n\twriteVersionedHelperForServiceTest(t, unrelatedRuntime, \"9.9.9\")\n\tif err := helperruntime.Activate(unrelatedRoot, \"v9.9.9\"); err != nil {\n\t\tt.Fatalf(\"activate unrelated runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, unrelatedRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v9.9.9\")\n\tt.Setenv(helperruntime.EnvEntryPath, unrelatedEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: launcher,\n\t\targv0: launcher,\n\t\tstat: serviceExecutableStatForTest,\n\t})\n\n\tif got := managedTeamsMaterializationSource(launcher, launcher); got != launcher {\n\t\tt.Fatalf(\"materialization source = %q, want unrelated runtime environment ignored in favor of %q\", got, launcher)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallReplacesManagedDefaultSymlinkToGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.MkdirAll(filepath.Dir(managed), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed dir: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, managed); err != nil {\n\t\tt.Fatalf(\"symlink managed to go/bin: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tif info, err := os.Lstat(managed); err != nil {\n\t\tt.Fatalf(\"lstat managed helper: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(managed)\n\t\tt.Fatalf(\"managed helper should be materialized as a real file, still symlink to %q\", target)\n\t}\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired stable cxp entry, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tif err := os.MkdirAll(home, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir effective home: %v\", err)\n\t}\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallFailsClosedWhenStableHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), \"resolve stable Teams service home\") {\n\t\tt.Fatalf(\"service install error = %v, want stable-home resolution failure\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"failed service install should not write a unit, stat error = %v\", err)\n\t}\n}\n\nfunc TestTeamsRuntimeSafetyServiceEnvironmentDoesNotPersistWSLDynamicLoopbackProxyByDefaultCI(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\targv0: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s persisted dynamic WSL loopback proxy %q in the background service: %#v\", name, value, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentOmitsRemovedStaticProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"MIMO_API_KEY\", \"DEEPSEEK_API_KEY\"} {\n\t\tif env[name] != \"\" {\n\t\t\tt.Fatalf(\"removed static provider key %s should not be preserved for the Teams service: %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConfiguredExternalProviderEnvKeys(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigPath := filepath.Join(tmp, \"config.json\")\n\traw := `{\"version\":6,\"profiles\":[],\"modelConfigVersion\":1,\"modelCredentials\":{\"nvidia\":{\"apiKeyRef\":\"env:NVIDIA_API_KEY\"}},\"modelProfiles\":{\"nvidia/deepseek-v4\":{\"provider\":\"nvidia\",\"model\":\"nvidia/deepseek-v4\",\"apiKeyRef\":\"env:NVIDIA_API_KEY\",\"revision\":1}}}`\n\tif err := os.WriteFile(configPath, []byte(raw), 0o600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", configPath)\n\tt.Setenv(\"NVIDIA_API_KEY\", \"sk-nvidia-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tenv := teamsServiceEnvironment()\n\tif env[\"NVIDIA_API_KEY\"] != \"sk-nvidia-test\" {\n\t\tt.Fatalf(\"configured external provider env key was not preserved: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unrelated model key leaked into service environment: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, removed := range []string{\"DEEPSEEK_API_KEY\", \"MIMO_API_KEY\"} {\n\t\tif strings.Contains(unit, removed) {\n\t\t\tt.Fatalf(\"unit preserved removed static provider key %s:\\n%s\", removed, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + stableHome + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tif err := os.MkdirAll(home, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir effective home: %v\", err)\n\t}\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceDoctorUsesInstalledWSLStartupFallbackWithoutScheduledTaskRequirement(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tif err := os.WriteFile(markerPath, []byte(buildTeamsServiceWSLStartupFallbackConfig(backend.Name(), []string{\"-d\", \"Ubuntu\", \"--\", \"true\"})), 0o600); err != nil {\n\t\tt.Fatalf(\"write fallback marker: %v\", err)\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute fallback doctor: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service preferred backend: wsl-windows-task-scheduler\",\n\t\t\"Teams service effective backend: wsl-startup-watchdog\",\n\t\t\"WSL Startup watchdog fallback: ready\",\n\t\t\"ScheduledTask cmdlets are not required\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"fallback doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"fallback readiness calls = %#v, want wsl.exe check plus optional task probe\", runner.calls)\n\t}\n\tfirst := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(first, \"Get-Command wsl.exe\") || strings.Contains(first, \"Get-ScheduledTask -ErrorAction Stop\") {\n\t\tt.Fatalf(\"effective fallback readiness incorrectly requires ScheduledTask cmdlets: %s\", first)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLStartupFallbackDoctorRejectsMismatchedMarker(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tmarker string\n\t\twant string\n\t}{\n\t\t{name: \"wrong task\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=other-task\\nCommand=wsl.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want \\\"\" + backend.Name() + \"\\\"\"},\n\t\t{name: \"wrong command\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=powershell.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want wsl.exe\"},\n\t\t{name: \"invalid arguments\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=wsl.exe\\nArguments=\\\"unterminated\\n\", want: \"invalid Arguments\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif err := os.WriteFile(markerPath, []byte(tc.marker), 0o600); err != nil {\n\t\t\t\tt.Fatalf(\"write marker: %v\", err)\n\t\t\t}\n\t\t\terr := runTeamsServiceWSLStartupFallbackReadinessCheck(context.Background(), io.Discard, backend)\n\t\t\tif err == nil || !strings.Contains(err.Error(), tc.want) {\n\t\t\t\tt.Fatalf(\"readiness error = %v, want %q\", err, tc.want)\n\t\t\t}\n\t\t})\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"invalid markers should fail before PowerShell probes: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, os.Getenv(\"HOME\"))\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslInteropAvailable *bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLInteropAvailable := teamsServiceWSLInteropAvailable\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tif hooks.wslInteropAvailable != nil {\n\t\tteamsServiceWSLInteropAvailable = func() bool { return *hooks.wslInteropAvailable }\n\t} else if hooks.isWSL {\n\t\t// Unit tests that explicitly simulate WSL should not inherit the host\n\t\t// runner's binfmt state. Tests for unavailable interop opt out via the\n\t\t// explicit hook above.\n\t\tteamsServiceWSLInteropAvailable = func() bool { return true }\n\t}\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLInteropAvailable = prevWSLInteropAvailable\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "faff2bd38f7aa8790cb1f1f1f9fe7579278e9854": {"t": 1785755425.6344178, "n": 288839, "h": "a4ab875c99b1faf2", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperpath\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperruntime\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc TestPreferredCXPServiceEntryDoesNotUseUnrelatedRuntime(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\truntimeDir := filepath.Join(tmp, \"go-bin\")\n\tmanagedDir := filepath.Join(tmp, \"managed-bin\")\n\tfor _, dir := range []string{runtimeDir, managedDir} {\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcurrentEntry := filepath.Join(runtimeDir, helperruntime.BinaryName(runtime.GOOS))\n\tmanagedPath := filepath.Join(managedDir, helperpath.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, currentEntry, \"runtime\", 0o755)\n\twriteCLIFile(t, managedPath, \"managed\", 0o755)\n\troot := filepath.Join(runtimeDir, \".cxp-runtime\")\n\tif err := helperruntime.Activate(root, \"v1.2.3\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, root)\n\tt.Setenv(helperruntime.EnvEntryPath, currentEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: runtime.GOOS, stat: serviceExecutableStatForTest})\n\n\tif got := preferredCXPServiceEntry(managedPath); got != \"\" {\n\t\tt.Fatalf(\"unrelated runtime entry leaked into managed service spec: %q\", got)\n\t}\n\tmanagedCXP := filepath.Join(managedDir, helperruntime.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, managedCXP, \"stable\", 0o755)\n\tif got := preferredCXPServiceEntry(managedPath); got != managedCXP {\n\t\tt.Fatalf(\"preferred entry = %q, want managed stable entry %q\", got, managedCXP)\n\t}\n}\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + systemdQuoteArg(stableHome),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tif cfg.Spec.WorkingDir != stableHome {\n\t\tt.Fatalf(\"config working dir = %q, want stable home %q\", cfg.Spec.WorkingDir, stableHome)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stableCXP) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesCurrentImmutableRuntimeInsteadOfOldLauncher(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tgoBinDir := filepath.Join(tmp, \"go\", \"bin\")\n\tlauncher := filepath.Join(goBinDir, \"codex-proxy\")\n\tentry := filepath.Join(goBinDir, \"cxp\")\n\truntimeRoot := filepath.Join(goBinDir, \".cxp-runtime\")\n\timmutableRuntime := helperruntime.VersionPath(runtimeRoot, \"v1.2.4\", runtime.GOOS)\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, entry, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, immutableRuntime, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := helperruntime.Activate(runtimeRoot, \"v1.2.4\"); err != nil {\n\t\tt.Fatalf(\"activate immutable runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, runtimeRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v1.2.4\")\n\tt.Setenv(helperruntime.EnvEntryPath, entry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: immutableRuntime,\n\t\targv0: launcher,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was copied from the old stable launcher instead of the immutable runtime: %s\", managedVersion)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want immutable runtime version at %s\", record, managed)\n\t}\n}\n\nfunc TestManagedTeamsMaterializationIgnoresUnrelatedRuntimeEnvironment(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tlauncher := filepath.Join(tmp, \"fixture\", \"codex-proxy\")\n\tunrelatedEntry := filepath.Join(tmp, \"live\", \"cxp\")\n\tunrelatedRoot := filepath.Join(tmp, \"live\", \".cxp-runtime\")\n\tunrelatedRuntime := helperruntime.VersionPath(unrelatedRoot, \"v9.9.9\", runtime.GOOS)\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, unrelatedEntry, \"9.9.9\")\n\twriteVersionedHelperForServiceTest(t, unrelatedRuntime, \"9.9.9\")\n\tif err := helperruntime.Activate(unrelatedRoot, \"v9.9.9\"); err != nil {\n\t\tt.Fatalf(\"activate unrelated runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, unrelatedRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v9.9.9\")\n\tt.Setenv(helperruntime.EnvEntryPath, unrelatedEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: launcher,\n\t\targv0: launcher,\n\t\tstat: serviceExecutableStatForTest,\n\t})\n\n\tif got := managedTeamsMaterializationSource(launcher, launcher); got != launcher {\n\t\tt.Fatalf(\"materialization source = %q, want unrelated runtime environment ignored in favor of %q\", got, launcher)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallReplacesManagedDefaultSymlinkToGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.MkdirAll(filepath.Dir(managed), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed dir: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, managed); err != nil {\n\t\tt.Fatalf(\"symlink managed to go/bin: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tif info, err := os.Lstat(managed); err != nil {\n\t\tt.Fatalf(\"lstat managed helper: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(managed)\n\t\tt.Fatalf(\"managed helper should be materialized as a real file, still symlink to %q\", target)\n\t}\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired stable cxp entry, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tif err := os.MkdirAll(home, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir effective home: %v\", err)\n\t}\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallFailsClosedWhenStableHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err == nil || !strings.Contains(err.Error(), \"resolve stable Teams service home\") {\n\t\tt.Fatalf(\"service install error = %v, want stable-home resolution failure\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"failed service install should not write a unit, stat error = %v\", err)\n\t}\n}\n\nfunc TestTeamsRuntimeSafetyServiceEnvironmentDoesNotPersistWSLDynamicLoopbackProxyByDefaultCI(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\targv0: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s persisted dynamic WSL loopback proxy %q in the background service: %#v\", name, value, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentOmitsRemovedStaticProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"MIMO_API_KEY\", \"DEEPSEEK_API_KEY\"} {\n\t\tif env[name] != \"\" {\n\t\t\tt.Fatalf(\"removed static provider key %s should not be preserved for the Teams service: %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConfiguredExternalProviderEnvKeys(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigPath := filepath.Join(tmp, \"config.json\")\n\traw := `{\"version\":6,\"profiles\":[],\"modelConfigVersion\":1,\"modelCredentials\":{\"nvidia\":{\"apiKeyRef\":\"env:NVIDIA_API_KEY\"}},\"modelProfiles\":{\"nvidia/deepseek-v4\":{\"provider\":\"nvidia\",\"model\":\"nvidia/deepseek-v4\",\"apiKeyRef\":\"env:NVIDIA_API_KEY\",\"revision\":1}}}`\n\tif err := os.WriteFile(configPath, []byte(raw), 0o600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", configPath)\n\tt.Setenv(\"NVIDIA_API_KEY\", \"sk-nvidia-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tenv := teamsServiceEnvironment()\n\tif env[\"NVIDIA_API_KEY\"] != \"sk-nvidia-test\" {\n\t\tt.Fatalf(\"configured external provider env key was not preserved: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unrelated model key leaked into service environment: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, removed := range []string{\"DEEPSEEK_API_KEY\", \"MIMO_API_KEY\"} {\n\t\tif strings.Contains(unit, removed) {\n\t\t\tt.Fatalf(\"unit preserved removed static provider key %s:\\n%s\", removed, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tstableHome := filepath.Clean(os.Getenv(\"HOME\"))\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + stableHome + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tif err := os.MkdirAll(home, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir effective home: %v\", err)\n\t}\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceDoctorUsesInstalledWSLStartupFallbackWithoutScheduledTaskRequirement(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tif err := os.WriteFile(markerPath, []byte(buildTeamsServiceWSLStartupFallbackConfig(backend.Name(), []string{\"-d\", \"Ubuntu\", \"--\", \"true\"})), 0o600); err != nil {\n\t\tt.Fatalf(\"write fallback marker: %v\", err)\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute fallback doctor: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service preferred backend: wsl-windows-task-scheduler\",\n\t\t\"Teams service effective backend: wsl-startup-watchdog\",\n\t\t\"WSL Startup watchdog fallback: ready\",\n\t\t\"ScheduledTask cmdlets are not required\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"fallback doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"fallback readiness calls = %#v, want wsl.exe check plus optional task probe\", runner.calls)\n\t}\n\tfirst := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(first, \"Get-Command wsl.exe\") || strings.Contains(first, \"Get-ScheduledTask -ErrorAction Stop\") {\n\t\tt.Fatalf(\"effective fallback readiness incorrectly requires ScheduledTask cmdlets: %s\", first)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLStartupFallbackDoctorRejectsMismatchedMarker(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tmarker string\n\t\twant string\n\t}{\n\t\t{name: \"wrong task\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=other-task\\nCommand=wsl.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want \\\"\" + backend.Name() + \"\\\"\"},\n\t\t{name: \"wrong command\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=powershell.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want wsl.exe\"},\n\t\t{name: \"invalid arguments\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=wsl.exe\\nArguments=\\\"unterminated\\n\", want: \"invalid Arguments\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif err := os.WriteFile(markerPath, []byte(tc.marker), 0o600); err != nil {\n\t\t\t\tt.Fatalf(\"write marker: %v\", err)\n\t\t\t}\n\t\t\terr := runTeamsServiceWSLStartupFallbackReadinessCheck(context.Background(), io.Discard, backend)\n\t\t\tif err == nil || !strings.Contains(err.Error(), tc.want) {\n\t\t\t\tt.Fatalf(\"readiness error = %v, want %q\", err, tc.want)\n\t\t\t}\n\t\t})\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"invalid markers should fail before PowerShell probes: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, os.Getenv(\"HOME\"))\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslInteropAvailable *bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLInteropAvailable := teamsServiceWSLInteropAvailable\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tif hooks.wslInteropAvailable != nil {\n\t\tteamsServiceWSLInteropAvailable = func() bool { return *hooks.wslInteropAvailable }\n\t}\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLInteropAvailable = prevWSLInteropAvailable\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "5439500758b4124b1ff70f7b159bbefc3f5193e1": {"t": 1785755427.9491742, "n": 288702, "h": "9e688eadd763aa5e", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperpath\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperruntime\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc TestPreferredCXPServiceEntryDoesNotUseUnrelatedRuntime(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\truntimeDir := filepath.Join(tmp, \"go-bin\")\n\tmanagedDir := filepath.Join(tmp, \"managed-bin\")\n\tfor _, dir := range []string{runtimeDir, managedDir} {\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcurrentEntry := filepath.Join(runtimeDir, helperruntime.BinaryName(runtime.GOOS))\n\tmanagedPath := filepath.Join(managedDir, helperpath.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, currentEntry, \"runtime\", 0o755)\n\twriteCLIFile(t, managedPath, \"managed\", 0o755)\n\troot := filepath.Join(runtimeDir, \".cxp-runtime\")\n\tif err := helperruntime.Activate(root, \"v1.2.3\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, root)\n\tt.Setenv(helperruntime.EnvEntryPath, currentEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: runtime.GOOS, stat: serviceExecutableStatForTest})\n\n\tif got := preferredCXPServiceEntry(managedPath); got != \"\" {\n\t\tt.Fatalf(\"unrelated runtime entry leaked into managed service spec: %q\", got)\n\t}\n\tmanagedCXP := filepath.Join(managedDir, helperruntime.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, managedCXP, \"stable\", 0o755)\n\tif got := preferredCXPServiceEntry(managedPath); got != managedCXP {\n\t\tt.Fatalf(\"preferred entry = %q, want managed stable entry %q\", got, managedCXP)\n\t}\n}\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stableCXP) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesCurrentImmutableRuntimeInsteadOfOldLauncher(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tgoBinDir := filepath.Join(tmp, \"go\", \"bin\")\n\tlauncher := filepath.Join(goBinDir, \"codex-proxy\")\n\tentry := filepath.Join(goBinDir, \"cxp\")\n\truntimeRoot := filepath.Join(goBinDir, \".cxp-runtime\")\n\timmutableRuntime := helperruntime.VersionPath(runtimeRoot, \"v1.2.4\", runtime.GOOS)\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, entry, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, immutableRuntime, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := helperruntime.Activate(runtimeRoot, \"v1.2.4\"); err != nil {\n\t\tt.Fatalf(\"activate immutable runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, runtimeRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v1.2.4\")\n\tt.Setenv(helperruntime.EnvEntryPath, entry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: immutableRuntime,\n\t\targv0: launcher,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was copied from the old stable launcher instead of the immutable runtime: %s\", managedVersion)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want immutable runtime version at %s\", record, managed)\n\t}\n}\n\nfunc TestManagedTeamsMaterializationIgnoresUnrelatedRuntimeEnvironment(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tlauncher := filepath.Join(tmp, \"fixture\", \"codex-proxy\")\n\tunrelatedEntry := filepath.Join(tmp, \"live\", \"cxp\")\n\tunrelatedRoot := filepath.Join(tmp, \"live\", \".cxp-runtime\")\n\tunrelatedRuntime := helperruntime.VersionPath(unrelatedRoot, \"v9.9.9\", runtime.GOOS)\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, unrelatedEntry, \"9.9.9\")\n\twriteVersionedHelperForServiceTest(t, unrelatedRuntime, \"9.9.9\")\n\tif err := helperruntime.Activate(unrelatedRoot, \"v9.9.9\"); err != nil {\n\t\tt.Fatalf(\"activate unrelated runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, unrelatedRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v9.9.9\")\n\tt.Setenv(helperruntime.EnvEntryPath, unrelatedEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: launcher,\n\t\targv0: launcher,\n\t\tstat: serviceExecutableStatForTest,\n\t})\n\n\tif got := managedTeamsMaterializationSource(launcher, launcher); got != launcher {\n\t\tt.Fatalf(\"materialization source = %q, want unrelated runtime environment ignored in favor of %q\", got, launcher)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallReplacesManagedDefaultSymlinkToGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.MkdirAll(filepath.Dir(managed), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed dir: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, managed); err != nil {\n\t\tt.Fatalf(\"symlink managed to go/bin: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tif info, err := os.Lstat(managed); err != nil {\n\t\tt.Fatalf(\"lstat managed helper: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(managed)\n\t\tt.Fatalf(\"managed helper should be materialized as a real file, still symlink to %q\", target)\n\t}\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired stable cxp entry, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsRuntimeSafetyServiceEnvironmentDoesNotPersistWSLDynamicLoopbackProxyByDefaultCI(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\targv0: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s persisted dynamic WSL loopback proxy %q in the background service: %#v\", name, value, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentOmitsRemovedStaticProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"MIMO_API_KEY\", \"DEEPSEEK_API_KEY\"} {\n\t\tif env[name] != \"\" {\n\t\t\tt.Fatalf(\"removed static provider key %s should not be preserved for the Teams service: %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConfiguredExternalProviderEnvKeys(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigPath := filepath.Join(tmp, \"config.json\")\n\traw := `{\"version\":6,\"profiles\":[],\"modelConfigVersion\":1,\"modelCredentials\":{\"nvidia\":{\"apiKeyRef\":\"env:NVIDIA_API_KEY\"}},\"modelProfiles\":{\"nvidia/deepseek-v4\":{\"provider\":\"nvidia\",\"model\":\"nvidia/deepseek-v4\",\"apiKeyRef\":\"env:NVIDIA_API_KEY\",\"revision\":1}}}`\n\tif err := os.WriteFile(configPath, []byte(raw), 0o600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", configPath)\n\tt.Setenv(\"NVIDIA_API_KEY\", \"sk-nvidia-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tenv := teamsServiceEnvironment()\n\tif env[\"NVIDIA_API_KEY\"] != \"sk-nvidia-test\" {\n\t\tt.Fatalf(\"configured external provider env key was not preserved: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unrelated model key leaked into service environment: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, removed := range []string{\"DEEPSEEK_API_KEY\", \"MIMO_API_KEY\"} {\n\t\tif strings.Contains(unit, removed) {\n\t\t\tt.Fatalf(\"unit preserved removed static provider key %s:\\n%s\", removed, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceDoctorUsesInstalledWSLStartupFallbackWithoutScheduledTaskRequirement(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tif err := os.WriteFile(markerPath, []byte(buildTeamsServiceWSLStartupFallbackConfig(backend.Name(), []string{\"-d\", \"Ubuntu\", \"--\", \"true\"})), 0o600); err != nil {\n\t\tt.Fatalf(\"write fallback marker: %v\", err)\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute fallback doctor: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service preferred backend: wsl-windows-task-scheduler\",\n\t\t\"Teams service effective backend: wsl-startup-watchdog\",\n\t\t\"WSL Startup watchdog fallback: ready\",\n\t\t\"ScheduledTask cmdlets are not required\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"fallback doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"fallback readiness calls = %#v, want wsl.exe check plus optional task probe\", runner.calls)\n\t}\n\tfirst := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(first, \"Get-Command wsl.exe\") || strings.Contains(first, \"Get-ScheduledTask -ErrorAction Stop\") {\n\t\tt.Fatalf(\"effective fallback readiness incorrectly requires ScheduledTask cmdlets: %s\", first)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLStartupFallbackDoctorRejectsMismatchedMarker(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tmarker string\n\t\twant string\n\t}{\n\t\t{name: \"wrong task\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=other-task\\nCommand=wsl.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want \\\"\" + backend.Name() + \"\\\"\"},\n\t\t{name: \"wrong command\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=powershell.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want wsl.exe\"},\n\t\t{name: \"invalid arguments\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=wsl.exe\\nArguments=\\\"unterminated\\n\", want: \"invalid Arguments\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif err := os.WriteFile(markerPath, []byte(tc.marker), 0o600); err != nil {\n\t\t\t\tt.Fatalf(\"write marker: %v\", err)\n\t\t\t}\n\t\t\terr := runTeamsServiceWSLStartupFallbackReadinessCheck(context.Background(), io.Discard, backend)\n\t\t\tif err == nil || !strings.Contains(err.Error(), tc.want) {\n\t\t\t\tt.Fatalf(\"readiness error = %v, want %q\", err, tc.want)\n\t\t\t}\n\t\t})\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"invalid markers should fail before PowerShell probes: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslInteropAvailable *bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLInteropAvailable := teamsServiceWSLInteropAvailable\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tif hooks.wslInteropAvailable != nil {\n\t\tteamsServiceWSLInteropAvailable = func() bool { return *hooks.wslInteropAvailable }\n\t}\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLInteropAvailable = prevWSLInteropAvailable\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "998f45822bf739fa88401090aee1f0bea50935c4": {"t": 1785755430.9158213, "n": 288420, "h": "b877d004244f89a6", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperpath\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperruntime\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc TestPreferredCXPServiceEntryDoesNotUseUnrelatedRuntime(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\truntimeDir := filepath.Join(tmp, \"go-bin\")\n\tmanagedDir := filepath.Join(tmp, \"managed-bin\")\n\tfor _, dir := range []string{runtimeDir, managedDir} {\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcurrentEntry := filepath.Join(runtimeDir, helperruntime.BinaryName(runtime.GOOS))\n\tmanagedPath := filepath.Join(managedDir, helperpath.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, currentEntry, \"runtime\", 0o755)\n\twriteCLIFile(t, managedPath, \"managed\", 0o755)\n\troot := filepath.Join(runtimeDir, \".cxp-runtime\")\n\tif err := helperruntime.Activate(root, \"v1.2.3\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, root)\n\tt.Setenv(helperruntime.EnvEntryPath, currentEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: runtime.GOOS, stat: serviceExecutableStatForTest})\n\n\tif got := preferredCXPServiceEntry(managedPath); got != \"\" {\n\t\tt.Fatalf(\"unrelated runtime entry leaked into managed service spec: %q\", got)\n\t}\n\tmanagedCXP := filepath.Join(managedDir, helperruntime.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, managedCXP, \"stable\", 0o755)\n\tif got := preferredCXPServiceEntry(managedPath); got != managedCXP {\n\t\tt.Fatalf(\"preferred entry = %q, want managed stable entry %q\", got, managedCXP)\n\t}\n}\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stableCXP) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesCurrentImmutableRuntimeInsteadOfOldLauncher(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tgoBinDir := filepath.Join(tmp, \"go\", \"bin\")\n\tlauncher := filepath.Join(goBinDir, \"codex-proxy\")\n\tentry := filepath.Join(goBinDir, \"cxp\")\n\truntimeRoot := filepath.Join(goBinDir, \".cxp-runtime\")\n\timmutableRuntime := helperruntime.VersionPath(runtimeRoot, \"v1.2.4\", runtime.GOOS)\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, entry, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, immutableRuntime, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := helperruntime.Activate(runtimeRoot, \"v1.2.4\"); err != nil {\n\t\tt.Fatalf(\"activate immutable runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, runtimeRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v1.2.4\")\n\tt.Setenv(helperruntime.EnvEntryPath, entry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: immutableRuntime,\n\t\targv0: launcher,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was copied from the old stable launcher instead of the immutable runtime: %s\", managedVersion)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want immutable runtime version at %s\", record, managed)\n\t}\n}\n\nfunc TestManagedTeamsMaterializationIgnoresUnrelatedRuntimeEnvironment(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tlauncher := filepath.Join(tmp, \"fixture\", \"codex-proxy\")\n\tunrelatedEntry := filepath.Join(tmp, \"live\", \"cxp\")\n\tunrelatedRoot := filepath.Join(tmp, \"live\", \".cxp-runtime\")\n\tunrelatedRuntime := helperruntime.VersionPath(unrelatedRoot, \"v9.9.9\", runtime.GOOS)\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, unrelatedEntry, \"9.9.9\")\n\twriteVersionedHelperForServiceTest(t, unrelatedRuntime, \"9.9.9\")\n\tif err := helperruntime.Activate(unrelatedRoot, \"v9.9.9\"); err != nil {\n\t\tt.Fatalf(\"activate unrelated runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, unrelatedRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v9.9.9\")\n\tt.Setenv(helperruntime.EnvEntryPath, unrelatedEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: launcher,\n\t\targv0: launcher,\n\t\tstat: serviceExecutableStatForTest,\n\t})\n\n\tif got := managedTeamsMaterializationSource(launcher, launcher); got != launcher {\n\t\tt.Fatalf(\"materialization source = %q, want unrelated runtime environment ignored in favor of %q\", got, launcher)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallReplacesManagedDefaultSymlinkToGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.MkdirAll(filepath.Dir(managed), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed dir: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, managed); err != nil {\n\t\tt.Fatalf(\"symlink managed to go/bin: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tif info, err := os.Lstat(managed); err != nil {\n\t\tt.Fatalf(\"lstat managed helper: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(managed)\n\t\tt.Fatalf(\"managed helper should be materialized as a real file, still symlink to %q\", target)\n\t}\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired stable cxp entry, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsRuntimeSafetyServiceEnvironmentDoesNotPersistWSLDynamicLoopbackProxyByDefaultCI(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\targv0: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s persisted dynamic WSL loopback proxy %q in the background service: %#v\", name, value, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentOmitsRemovedStaticProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"MIMO_API_KEY\", \"DEEPSEEK_API_KEY\"} {\n\t\tif env[name] != \"\" {\n\t\t\tt.Fatalf(\"removed static provider key %s should not be preserved for the Teams service: %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConfiguredExternalProviderEnvKeys(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigPath := filepath.Join(tmp, \"config.json\")\n\traw := `{\"version\":6,\"profiles\":[],\"modelConfigVersion\":1,\"modelCredentials\":{\"nvidia\":{\"apiKeyRef\":\"env:NVIDIA_API_KEY\"}},\"modelProfiles\":{\"nvidia/deepseek-v4\":{\"provider\":\"nvidia\",\"model\":\"nvidia/deepseek-v4\",\"apiKeyRef\":\"env:NVIDIA_API_KEY\",\"revision\":1}}}`\n\tif err := os.WriteFile(configPath, []byte(raw), 0o600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", configPath)\n\tt.Setenv(\"NVIDIA_API_KEY\", \"sk-nvidia-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tenv := teamsServiceEnvironment()\n\tif env[\"NVIDIA_API_KEY\"] != \"sk-nvidia-test\" {\n\t\tt.Fatalf(\"configured external provider env key was not preserved: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unrelated model key leaked into service environment: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, removed := range []string{\"DEEPSEEK_API_KEY\", \"MIMO_API_KEY\"} {\n\t\tif strings.Contains(unit, removed) {\n\t\t\tt.Fatalf(\"unit preserved removed static provider key %s:\\n%s\", removed, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceDoctorUsesInstalledWSLStartupFallbackWithoutScheduledTaskRequirement(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tif err := os.WriteFile(markerPath, []byte(buildTeamsServiceWSLStartupFallbackConfig(backend.Name(), []string{\"-d\", \"Ubuntu\", \"--\", \"true\"})), 0o600); err != nil {\n\t\tt.Fatalf(\"write fallback marker: %v\", err)\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute fallback doctor: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service preferred backend: wsl-windows-task-scheduler\",\n\t\t\"Teams service effective backend: wsl-startup-watchdog\",\n\t\t\"WSL Startup watchdog fallback: ready\",\n\t\t\"ScheduledTask cmdlets are not required\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"fallback doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"fallback readiness calls = %#v, want wsl.exe check plus optional task probe\", runner.calls)\n\t}\n\tfirst := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(first, \"Get-Command wsl.exe\") || strings.Contains(first, \"Get-ScheduledTask -ErrorAction Stop\") {\n\t\tt.Fatalf(\"effective fallback readiness incorrectly requires ScheduledTask cmdlets: %s\", first)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLStartupFallbackDoctorRejectsMismatchedMarker(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tmarker string\n\t\twant string\n\t}{\n\t\t{name: \"wrong task\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=other-task\\nCommand=wsl.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want \\\"\" + backend.Name() + \"\\\"\"},\n\t\t{name: \"wrong command\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=powershell.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want wsl.exe\"},\n\t\t{name: \"invalid arguments\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=wsl.exe\\nArguments=\\\"unterminated\\n\", want: \"invalid Arguments\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif err := os.WriteFile(markerPath, []byte(tc.marker), 0o600); err != nil {\n\t\t\t\tt.Fatalf(\"write marker: %v\", err)\n\t\t\t}\n\t\t\terr := runTeamsServiceWSLStartupFallbackReadinessCheck(context.Background(), io.Discard, backend)\n\t\t\tif err == nil || !strings.Contains(err.Error(), tc.want) {\n\t\t\t\tt.Fatalf(\"readiness error = %v, want %q\", err, tc.want)\n\t\t\t}\n\t\t})\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"invalid markers should fail before PowerShell probes: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "a9dccbe5e2c86f53b26b513f1007b70a4511f8fa": {"t": 1785755433.5633655, "n": 287883, "h": "67baa5453bae495f", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperpath\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperruntime\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc TestPreferredCXPServiceEntryDoesNotUseUnrelatedRuntime(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\truntimeDir := filepath.Join(tmp, \"go-bin\")\n\tmanagedDir := filepath.Join(tmp, \"managed-bin\")\n\tfor _, dir := range []string{runtimeDir, managedDir} {\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcurrentEntry := filepath.Join(runtimeDir, helperruntime.BinaryName(runtime.GOOS))\n\tmanagedPath := filepath.Join(managedDir, helperpath.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, currentEntry, \"runtime\", 0o755)\n\twriteCLIFile(t, managedPath, \"managed\", 0o755)\n\troot := filepath.Join(runtimeDir, \".cxp-runtime\")\n\tif err := helperruntime.Activate(root, \"v1.2.3\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, root)\n\tt.Setenv(helperruntime.EnvEntryPath, currentEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: runtime.GOOS, stat: serviceExecutableStatForTest})\n\n\tif got := preferredCXPServiceEntry(managedPath); got != \"\" {\n\t\tt.Fatalf(\"unrelated runtime entry leaked into managed service spec: %q\", got)\n\t}\n\tmanagedCXP := filepath.Join(managedDir, helperruntime.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, managedCXP, \"stable\", 0o755)\n\tif got := preferredCXPServiceEntry(managedPath); got != managedCXP {\n\t\tt.Fatalf(\"preferred entry = %q, want managed stable entry %q\", got, managedCXP)\n\t}\n}\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stableCXP) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesCurrentImmutableRuntimeInsteadOfOldLauncher(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tgoBinDir := filepath.Join(tmp, \"go\", \"bin\")\n\tlauncher := filepath.Join(goBinDir, \"codex-proxy\")\n\tentry := filepath.Join(goBinDir, \"cxp\")\n\truntimeRoot := filepath.Join(goBinDir, \".cxp-runtime\")\n\timmutableRuntime := helperruntime.VersionPath(runtimeRoot, \"v1.2.4\", runtime.GOOS)\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, entry, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, immutableRuntime, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := helperruntime.Activate(runtimeRoot, \"v1.2.4\"); err != nil {\n\t\tt.Fatalf(\"activate immutable runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, runtimeRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v1.2.4\")\n\tt.Setenv(helperruntime.EnvEntryPath, entry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: immutableRuntime,\n\t\targv0: launcher,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was copied from the old stable launcher instead of the immutable runtime: %s\", managedVersion)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want immutable runtime version at %s\", record, managed)\n\t}\n}\n\nfunc TestManagedTeamsMaterializationIgnoresUnrelatedRuntimeEnvironment(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tlauncher := filepath.Join(tmp, \"fixture\", \"codex-proxy\")\n\tunrelatedEntry := filepath.Join(tmp, \"live\", \"cxp\")\n\tunrelatedRoot := filepath.Join(tmp, \"live\", \".cxp-runtime\")\n\tunrelatedRuntime := helperruntime.VersionPath(unrelatedRoot, \"v9.9.9\", runtime.GOOS)\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, unrelatedEntry, \"9.9.9\")\n\twriteVersionedHelperForServiceTest(t, unrelatedRuntime, \"9.9.9\")\n\tif err := helperruntime.Activate(unrelatedRoot, \"v9.9.9\"); err != nil {\n\t\tt.Fatalf(\"activate unrelated runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, unrelatedRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v9.9.9\")\n\tt.Setenv(helperruntime.EnvEntryPath, unrelatedEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: launcher,\n\t\targv0: launcher,\n\t\tstat: serviceExecutableStatForTest,\n\t})\n\n\tif got := managedTeamsMaterializationSource(launcher, launcher); got != launcher {\n\t\tt.Fatalf(\"materialization source = %q, want unrelated runtime environment ignored in favor of %q\", got, launcher)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallReplacesManagedDefaultSymlinkToGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.MkdirAll(filepath.Dir(managed), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed dir: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, managed); err != nil {\n\t\tt.Fatalf(\"symlink managed to go/bin: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tif info, err := os.Lstat(managed); err != nil {\n\t\tt.Fatalf(\"lstat managed helper: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(managed)\n\t\tt.Fatalf(\"managed helper should be materialized as a real file, still symlink to %q\", target)\n\t}\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired stable cxp entry, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentOmitsRemovedStaticProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"MIMO_API_KEY\", \"DEEPSEEK_API_KEY\"} {\n\t\tif env[name] != \"\" {\n\t\t\tt.Fatalf(\"removed static provider key %s should not be preserved for the Teams service: %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConfiguredExternalProviderEnvKeys(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigPath := filepath.Join(tmp, \"config.json\")\n\traw := `{\"version\":6,\"profiles\":[],\"modelConfigVersion\":1,\"modelCredentials\":{\"nvidia\":{\"apiKeyRef\":\"env:NVIDIA_API_KEY\"}},\"modelProfiles\":{\"nvidia/deepseek-v4\":{\"provider\":\"nvidia\",\"model\":\"nvidia/deepseek-v4\",\"apiKeyRef\":\"env:NVIDIA_API_KEY\",\"revision\":1}}}`\n\tif err := os.WriteFile(configPath, []byte(raw), 0o600); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", configPath)\n\tt.Setenv(\"NVIDIA_API_KEY\", \"sk-nvidia-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tenv := teamsServiceEnvironment()\n\tif env[\"NVIDIA_API_KEY\"] != \"sk-nvidia-test\" {\n\t\tt.Fatalf(\"configured external provider env key was not preserved: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unrelated model key leaked into service environment: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, removed := range []string{\"DEEPSEEK_API_KEY\", \"MIMO_API_KEY\"} {\n\t\tif strings.Contains(unit, removed) {\n\t\t\tt.Fatalf(\"unit preserved removed static provider key %s:\\n%s\", removed, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceDoctorUsesInstalledWSLStartupFallbackWithoutScheduledTaskRequirement(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tif err := os.WriteFile(markerPath, []byte(buildTeamsServiceWSLStartupFallbackConfig(backend.Name(), []string{\"-d\", \"Ubuntu\", \"--\", \"true\"})), 0o600); err != nil {\n\t\tt.Fatalf(\"write fallback marker: %v\", err)\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute fallback doctor: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service preferred backend: wsl-windows-task-scheduler\",\n\t\t\"Teams service effective backend: wsl-startup-watchdog\",\n\t\t\"WSL Startup watchdog fallback: ready\",\n\t\t\"ScheduledTask cmdlets are not required\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"fallback doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"fallback readiness calls = %#v, want wsl.exe check plus optional task probe\", runner.calls)\n\t}\n\tfirst := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(first, \"Get-Command wsl.exe\") || strings.Contains(first, \"Get-ScheduledTask -ErrorAction Stop\") {\n\t\tt.Fatalf(\"effective fallback readiness incorrectly requires ScheduledTask cmdlets: %s\", first)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLStartupFallbackDoctorRejectsMismatchedMarker(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tmarker string\n\t\twant string\n\t}{\n\t\t{name: \"wrong task\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=other-task\\nCommand=wsl.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want \\\"\" + backend.Name() + \"\\\"\"},\n\t\t{name: \"wrong command\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=powershell.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want wsl.exe\"},\n\t\t{name: \"invalid arguments\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=wsl.exe\\nArguments=\\\"unterminated\\n\", want: \"invalid Arguments\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif err := os.WriteFile(markerPath, []byte(tc.marker), 0o600); err != nil {\n\t\t\t\tt.Fatalf(\"write marker: %v\", err)\n\t\t\t}\n\t\t\terr := runTeamsServiceWSLStartupFallbackReadinessCheck(context.Background(), io.Discard, backend)\n\t\t\tif err == nil || !strings.Contains(err.Error(), tc.want) {\n\t\t\t\tt.Fatalf(\"readiness error = %v, want %q\", err, tc.want)\n\t\t\t}\n\t\t})\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"invalid markers should fail before PowerShell probes: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "ac1b844978b58cce8e1b0fbc6e294fdf1c270e78": {"t": 1785755436.3806453, "n": 286981, "h": "d306ab773e64b338", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperpath\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperruntime\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc TestPreferredCXPServiceEntryDoesNotUseUnrelatedRuntime(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\truntimeDir := filepath.Join(tmp, \"go-bin\")\n\tmanagedDir := filepath.Join(tmp, \"managed-bin\")\n\tfor _, dir := range []string{runtimeDir, managedDir} {\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcurrentEntry := filepath.Join(runtimeDir, helperruntime.BinaryName(runtime.GOOS))\n\tmanagedPath := filepath.Join(managedDir, helperpath.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, currentEntry, \"runtime\", 0o755)\n\twriteCLIFile(t, managedPath, \"managed\", 0o755)\n\troot := filepath.Join(runtimeDir, \".cxp-runtime\")\n\tif err := helperruntime.Activate(root, \"v1.2.3\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, root)\n\tt.Setenv(helperruntime.EnvEntryPath, currentEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: runtime.GOOS, stat: serviceExecutableStatForTest})\n\n\tif got := preferredCXPServiceEntry(managedPath); got != \"\" {\n\t\tt.Fatalf(\"unrelated runtime entry leaked into managed service spec: %q\", got)\n\t}\n\tmanagedCXP := filepath.Join(managedDir, helperruntime.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, managedCXP, \"stable\", 0o755)\n\tif got := preferredCXPServiceEntry(managedPath); got != managedCXP {\n\t\tt.Fatalf(\"preferred entry = %q, want managed stable entry %q\", got, managedCXP)\n\t}\n}\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stableCXP) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesCurrentImmutableRuntimeInsteadOfOldLauncher(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tgoBinDir := filepath.Join(tmp, \"go\", \"bin\")\n\tlauncher := filepath.Join(goBinDir, \"codex-proxy\")\n\tentry := filepath.Join(goBinDir, \"cxp\")\n\truntimeRoot := filepath.Join(goBinDir, \".cxp-runtime\")\n\timmutableRuntime := helperruntime.VersionPath(runtimeRoot, \"v1.2.4\", runtime.GOOS)\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, entry, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, immutableRuntime, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := helperruntime.Activate(runtimeRoot, \"v1.2.4\"); err != nil {\n\t\tt.Fatalf(\"activate immutable runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, runtimeRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v1.2.4\")\n\tt.Setenv(helperruntime.EnvEntryPath, entry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: immutableRuntime,\n\t\targv0: launcher,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was copied from the old stable launcher instead of the immutable runtime: %s\", managedVersion)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want immutable runtime version at %s\", record, managed)\n\t}\n}\n\nfunc TestManagedTeamsMaterializationIgnoresUnrelatedRuntimeEnvironment(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tlauncher := filepath.Join(tmp, \"fixture\", \"codex-proxy\")\n\tunrelatedEntry := filepath.Join(tmp, \"live\", \"cxp\")\n\tunrelatedRoot := filepath.Join(tmp, \"live\", \".cxp-runtime\")\n\tunrelatedRuntime := helperruntime.VersionPath(unrelatedRoot, \"v9.9.9\", runtime.GOOS)\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, unrelatedEntry, \"9.9.9\")\n\twriteVersionedHelperForServiceTest(t, unrelatedRuntime, \"9.9.9\")\n\tif err := helperruntime.Activate(unrelatedRoot, \"v9.9.9\"); err != nil {\n\t\tt.Fatalf(\"activate unrelated runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, unrelatedRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v9.9.9\")\n\tt.Setenv(helperruntime.EnvEntryPath, unrelatedEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: launcher,\n\t\targv0: launcher,\n\t\tstat: serviceExecutableStatForTest,\n\t})\n\n\tif got := managedTeamsMaterializationSource(launcher, launcher); got != launcher {\n\t\tt.Fatalf(\"materialization source = %q, want unrelated runtime environment ignored in favor of %q\", got, launcher)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallReplacesManagedDefaultSymlinkToGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.MkdirAll(filepath.Dir(managed), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed dir: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, managed); err != nil {\n\t\tt.Fatalf(\"symlink managed to go/bin: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tif info, err := os.Lstat(managed); err != nil {\n\t\tt.Fatalf(\"lstat managed helper: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(managed)\n\t\tt.Fatalf(\"managed helper should be materialized as a real file, still symlink to %q\", target)\n\t}\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired stable cxp entry, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceDoctorUsesInstalledWSLStartupFallbackWithoutScheduledTaskRequirement(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tif err := os.WriteFile(markerPath, []byte(buildTeamsServiceWSLStartupFallbackConfig(backend.Name(), []string{\"-d\", \"Ubuntu\", \"--\", \"true\"})), 0o600); err != nil {\n\t\tt.Fatalf(\"write fallback marker: %v\", err)\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute fallback doctor: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service preferred backend: wsl-windows-task-scheduler\",\n\t\t\"Teams service effective backend: wsl-startup-watchdog\",\n\t\t\"WSL Startup watchdog fallback: ready\",\n\t\t\"ScheduledTask cmdlets are not required\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"fallback doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"fallback readiness calls = %#v, want wsl.exe check plus optional task probe\", runner.calls)\n\t}\n\tfirst := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(first, \"Get-Command wsl.exe\") || strings.Contains(first, \"Get-ScheduledTask -ErrorAction Stop\") {\n\t\tt.Fatalf(\"effective fallback readiness incorrectly requires ScheduledTask cmdlets: %s\", first)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLStartupFallbackDoctorRejectsMismatchedMarker(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\tbackend := teamsServiceWSLWindowsTaskBackend{}\n\tmarkerPath, err := backend.startupFallbackMarkerPath()\n\tif err != nil {\n\t\tt.Fatalf(\"startupFallbackMarkerPath: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(markerPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir marker dir: %v\", err)\n\t}\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tmarker string\n\t\twant string\n\t}{\n\t\t{name: \"wrong task\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=other-task\\nCommand=wsl.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want \\\"\" + backend.Name() + \"\\\"\"},\n\t\t{name: \"wrong command\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=powershell.exe\\nArguments=-d Ubuntu -- true\\n\", want: \"want wsl.exe\"},\n\t\t{name: \"invalid arguments\", marker: \"Fallback=Windows Startup watchdog\\nTaskName=\" + backend.Name() + \"\\nCommand=wsl.exe\\nArguments=\\\"unterminated\\n\", want: \"invalid Arguments\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif err := os.WriteFile(markerPath, []byte(tc.marker), 0o600); err != nil {\n\t\t\t\tt.Fatalf(\"write marker: %v\", err)\n\t\t\t}\n\t\t\terr := runTeamsServiceWSLStartupFallbackReadinessCheck(context.Background(), io.Discard, backend)\n\t\t\tif err == nil || !strings.Contains(err.Error(), tc.want) {\n\t\t\t\tt.Fatalf(\"readiness error = %v, want %q\", err, tc.want)\n\t\t\t}\n\t\t})\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"invalid markers should fail before PowerShell probes: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "b4712076fecf255c1f7086d748d877e50e9d6f3e": {"t": 1785755438.392715, "n": 282766, "h": "975cac69f7a64786", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperpath\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperruntime\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc TestPreferredCXPServiceEntryDoesNotUseUnrelatedRuntime(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\truntimeDir := filepath.Join(tmp, \"go-bin\")\n\tmanagedDir := filepath.Join(tmp, \"managed-bin\")\n\tfor _, dir := range []string{runtimeDir, managedDir} {\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcurrentEntry := filepath.Join(runtimeDir, helperruntime.BinaryName(runtime.GOOS))\n\tmanagedPath := filepath.Join(managedDir, helperpath.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, currentEntry, \"runtime\", 0o755)\n\twriteCLIFile(t, managedPath, \"managed\", 0o755)\n\troot := filepath.Join(runtimeDir, \".cxp-runtime\")\n\tif err := helperruntime.Activate(root, \"v1.2.3\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, root)\n\tt.Setenv(helperruntime.EnvEntryPath, currentEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: runtime.GOOS, stat: serviceExecutableStatForTest})\n\n\tif got := preferredCXPServiceEntry(managedPath); got != \"\" {\n\t\tt.Fatalf(\"unrelated runtime entry leaked into managed service spec: %q\", got)\n\t}\n\tmanagedCXP := filepath.Join(managedDir, helperruntime.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, managedCXP, \"stable\", 0o755)\n\tif got := preferredCXPServiceEntry(managedPath); got != managedCXP {\n\t\tt.Fatalf(\"preferred entry = %q, want managed stable entry %q\", got, managedCXP)\n\t}\n}\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stableCXP) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesCurrentImmutableRuntimeInsteadOfOldLauncher(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tgoBinDir := filepath.Join(tmp, \"go\", \"bin\")\n\tlauncher := filepath.Join(goBinDir, \"codex-proxy\")\n\tentry := filepath.Join(goBinDir, \"cxp\")\n\truntimeRoot := filepath.Join(goBinDir, \".cxp-runtime\")\n\timmutableRuntime := helperruntime.VersionPath(runtimeRoot, \"v1.2.4\", runtime.GOOS)\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, entry, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, immutableRuntime, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := helperruntime.Activate(runtimeRoot, \"v1.2.4\"); err != nil {\n\t\tt.Fatalf(\"activate immutable runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, runtimeRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v1.2.4\")\n\tt.Setenv(helperruntime.EnvEntryPath, entry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: immutableRuntime,\n\t\targv0: launcher,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was copied from the old stable launcher instead of the immutable runtime: %s\", managedVersion)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want immutable runtime version at %s\", record, managed)\n\t}\n}\n\nfunc TestManagedTeamsMaterializationIgnoresUnrelatedRuntimeEnvironment(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tlauncher := filepath.Join(tmp, \"fixture\", \"codex-proxy\")\n\tunrelatedEntry := filepath.Join(tmp, \"live\", \"cxp\")\n\tunrelatedRoot := filepath.Join(tmp, \"live\", \".cxp-runtime\")\n\tunrelatedRuntime := helperruntime.VersionPath(unrelatedRoot, \"v9.9.9\", runtime.GOOS)\n\twriteVersionedHelperForServiceTest(t, launcher, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, unrelatedEntry, \"9.9.9\")\n\twriteVersionedHelperForServiceTest(t, unrelatedRuntime, \"9.9.9\")\n\tif err := helperruntime.Activate(unrelatedRoot, \"v9.9.9\"); err != nil {\n\t\tt.Fatalf(\"activate unrelated runtime: %v\", err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, unrelatedRoot)\n\tt.Setenv(helperruntime.EnvRuntimeVersion, \"v9.9.9\")\n\tt.Setenv(helperruntime.EnvEntryPath, unrelatedEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: runtime.GOOS,\n\t\texe: launcher,\n\t\targv0: launcher,\n\t\tstat: serviceExecutableStatForTest,\n\t})\n\n\tif got := managedTeamsMaterializationSource(launcher, launcher); got != launcher {\n\t\tt.Fatalf(\"materialization source = %q, want unrelated runtime environment ignored in favor of %q\", got, launcher)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallReplacesManagedDefaultSymlinkToGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.MkdirAll(filepath.Dir(managed), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed dir: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, managed); err != nil {\n\t\tt.Fatalf(\"symlink managed to go/bin: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tif info, err := os.Lstat(managed); err != nil {\n\t\tt.Fatalf(\"lstat managed helper: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(managed)\n\t\tt.Fatalf(\"managed helper should be materialized as a real file, still symlink to %q\", target)\n\t}\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired stable cxp entry, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "c443e99ff5530d922212f552c1e633732910b7d6": {"t": 1785755441.4715407, "n": 279235, "h": "b7a1ec4d83e389da", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperpath\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/helperruntime\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc TestPreferredCXPServiceEntryDoesNotUseUnrelatedRuntime(t *testing.T) {\n\tlockCLITestHooks(t)\n\ttmp := t.TempDir()\n\truntimeDir := filepath.Join(tmp, \"go-bin\")\n\tmanagedDir := filepath.Join(tmp, \"managed-bin\")\n\tfor _, dir := range []string{runtimeDir, managedDir} {\n\t\tif err := os.MkdirAll(dir, 0o755); err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n\tcurrentEntry := filepath.Join(runtimeDir, helperruntime.BinaryName(runtime.GOOS))\n\tmanagedPath := filepath.Join(managedDir, helperpath.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, currentEntry, \"runtime\", 0o755)\n\twriteCLIFile(t, managedPath, \"managed\", 0o755)\n\troot := filepath.Join(runtimeDir, \".cxp-runtime\")\n\tif err := helperruntime.Activate(root, \"v1.2.3\"); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Setenv(helperruntime.EnvRuntime, \"1\")\n\tt.Setenv(helperruntime.EnvRuntimeRoot, root)\n\tt.Setenv(helperruntime.EnvEntryPath, currentEntry)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: runtime.GOOS, stat: serviceExecutableStatForTest})\n\n\tif got := preferredCXPServiceEntry(managedPath); got != \"\" {\n\t\tt.Fatalf(\"unrelated runtime entry leaked into managed service spec: %q\", got)\n\t}\n\tmanagedCXP := filepath.Join(managedDir, helperruntime.BinaryName(runtime.GOOS))\n\twriteCLIFile(t, managedCXP, \"stable\", 0o755)\n\tif got := preferredCXPServiceEntry(managedPath); got != managedCXP {\n\t\tt.Fatalf(\"preferred entry = %q, want managed stable entry %q\", got, managedCXP)\n\t}\n}\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stableCXP) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallReplacesManagedDefaultSymlinkToGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.MkdirAll(filepath.Dir(managed), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed dir: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, managed); err != nil {\n\t\tt.Fatalf(\"symlink managed to go/bin: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tif info, err := os.Lstat(managed); err != nil {\n\t\tt.Fatalf(\"lstat managed helper: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(managed)\n\t\tt.Fatalf(\"managed helper should be materialized as a real file, still symlink to %q\", target)\n\t}\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at stable cxp entry:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(cxp)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired stable cxp entry, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tstableCXP := filepath.Join(filepath.Dir(managed), \"cxp\")\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stableCXP),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "aaa57a888d575f483a5c0456e40bc44d149f6453": {"t": 1785755443.3586767, "n": 277594, "h": "105a2d00eb15ce83", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(managed) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallReplacesManagedDefaultSymlinkToGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.MkdirAll(filepath.Dir(managed), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed dir: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, managed); err != nil {\n\t\tt.Fatalf(\"symlink managed to go/bin: %v\", err)\n\t}\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tif info, err := os.Lstat(managed); err != nil {\n\t\tt.Fatalf(\"lstat managed helper: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(managed)\n\t\tt.Fatalf(\"managed helper should be materialized as a real file, still symlink to %q\", target)\n\t}\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired managed helper, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "05b6b2bf47dc60f802a9d6efaad8180e1a30a034": {"t": 1785755445.4827833, "n": 274803, "h": "fa914306a1fd7d07", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(managed) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsStaleManagedCXPShimWhenRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tif err := os.Symlink(goBin, cxp); err != nil {\n\t\tt.Fatalf(\"symlink stale cxp: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not repaired from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired managed helper, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "7939bfdca7efa56634f86c118707393e02766e2d": {"t": 1785755448.687626, "n": 272126, "h": "26f13a736768bb11", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{\n\t\tTargetPath: goBin,\n\t\tTargetSource: string(managedinstall.SourceCurrentExecutable),\n\t\tTargetState: string(managedinstall.StateManaged),\n\t\tVersion: \"1.2.4\",\n\t\tGOOS: \"linux\",\n\t\tGOARCH: runtime.GOARCH,\n\t}); err != nil {\n\t\tt.Fatalf(\"save go/bin install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(managed) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired managed helper, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "a2092c8fb972290843100ade6179622b73920376": {"t": 1785755451.2043893, "n": 271643, "h": "b4efbe52fced6091", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(managed) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsMaterializingSameTargetThroughSymlinkedLocal(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\thome := filepath.Join(tmp, \"home\")\n\tphysicalLocal := filepath.Join(tmp, \"local-overflow\")\n\tphysicalBin := filepath.Join(physicalLocal, \"bin\")\n\tif err := os.MkdirAll(filepath.Dir(filepath.Join(home, \".local\")), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir home: %v\", err)\n\t}\n\tif err := os.MkdirAll(physicalBin, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir physical bin: %v\", err)\n\t}\n\tif err := os.Symlink(physicalLocal, filepath.Join(home, \".local\")); err != nil {\n\t\tt.Fatalf(\"symlink .local: %v\", err)\n\t}\n\tphysicalInstall := filepath.Join(physicalBin, \"codex-proxy\")\n\tlogicalInstall := filepath.Join(home, \".local\", \"bin\", \"codex-proxy\")\n\tlogicalCXP := filepath.Join(home, \".local\", \"bin\", \"cxp\")\n\trecordPath := filepath.Join(tmp, \"install.json\")\n\twriteVersionedHelperForServiceTest(t, physicalInstall, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: physicalInstall,\n\t\tcwd: tmp,\n\t})\n\n\ttarget := managedinstall.Target{\n\t\tPath: logicalInstall,\n\t\tSource: managedinstall.SourceDefault,\n\t\tState: managedinstall.StateManaged,\n\t\tRecordPath: recordPath,\n\t}\n\tif err := defaultMaterializeManagedTeamsInstallTarget(context.Background(), target); err != nil {\n\t\tt.Fatalf(\"materialize symlinked same target: %v\", err)\n\t}\n\tif info, err := os.Lstat(physicalInstall); err != nil {\n\t\tt.Fatalf(\"lstat physical install: %v\", err)\n\t} else if info.Mode()&os.ModeSymlink != 0 {\n\t\ttarget, _ := os.Readlink(physicalInstall)\n\t\tt.Fatalf(\"physical install was replaced by symlink to %q\", target)\n\t}\n\tif _, err := os.Lstat(logicalCXP); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not create a cxp shim for the same symlinked target, err=%v\", err)\n\t}\n\tif _, err := os.Stat(recordPath); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"materialization should not write a record for the same symlinked target, err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired managed helper, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "a3d6fed6fdc490399b9856a7b6302454fa65c0a9": {"t": 1785755453.5614095, "n": 269534, "h": "b7b5cfd9a79125df", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/appdirs\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(tmp, \"state\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc cliStatePathForTest(t *testing.T, parts ...string) string {\n\tt.Helper()\n\tpath, err := appdirs.StatePath(parts...)\n\tif err != nil {\n\t\tt.Fatalf(\"appdirs.StatePath: %v\", err)\n\t}\n\treturn path\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"XDG_STATE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tcliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable state-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirMigratesLegacyStatusWhenNewDirExists(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\"}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(legacyDir, teamsServiceLocalSupervisorLockName), []byte(\"\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy lock: %v\", err)\n\t}\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twantStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif statusPath != wantStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, wantStatus)\n\t}\n\tassertCLIFileContent(t, wantStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tassertCLIFileContent(t, legacyStatus, `{\"version\":1,\"state\":\"running\"}`)\n\tif _, err := os.Stat(filepath.Join(newDir, teamsServiceLocalSupervisorLockName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"lock file should not be copied, stat err = %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesStaleNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":111}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale new status: %v\", err)\n\t}\n\tif err := os.Chtimes(newStatus, time.Unix(100, 0), time.Unix(100, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes new status: %v\", err)\n\t}\n\tif err := os.Chtimes(legacyStatus, time.Unix(200, 0), time.Unix(200, 0)); err != nil {\n\t\tt.Fatalf(\"chtimes legacy status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirRefreshesCorruptNewStatusCI(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tlegacyDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\")\n\tnewDir := cliStatePathForTest(t, \"teams\", \"service\", \"local-supervisor\", \"run\")\n\tif err := os.MkdirAll(legacyDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir legacy runtime: %v\", err)\n\t}\n\tif err := os.MkdirAll(newDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir new runtime: %v\", err)\n\t}\n\tlegacyStatus := filepath.Join(legacyDir, teamsServiceLocalSupervisorStatusName)\n\tnewStatus := filepath.Join(newDir, teamsServiceLocalSupervisorStatusName)\n\tif err := os.WriteFile(legacyStatus, []byte(`{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`), 0o600); err != nil {\n\t\tt.Fatalf(\"write legacy status: %v\", err)\n\t}\n\tif err := os.WriteFile(newStatus, []byte(`{\"version\":`), 0o600); err != nil {\n\t\tt.Fatalf(\"write corrupt new status: %v\", err)\n\t}\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif statusPath != newStatus {\n\t\tt.Fatalf(\"status path = %q, want %q\", statusPath, newStatus)\n\t}\n\tassertCLIFileContent(t, newStatus, `{\"version\":1,\"state\":\"running\",\"supervisor_pid\":222}`)\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tt.Setenv(\"XDG_STATE_HOME\", filepath.Join(t.TempDir(), \"state\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_STATE_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_STATE_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(managed) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired managed helper, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(appdirs.EnvStateDir, filepath.Join(tmp, \"state root\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(appdirs.EnvStateDir+\"=\"+filepath.Join(tmp, \"state root\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "f7a90ccf59c4d3ee842c8a5a6180d0d61db54e77": {"t": 1785755456.4936147, "n": 264422, "h": "4dba2e2aaaf3ae17", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(update.EnvInstallPath, \"\")\n\tt.Setenv(update.EnvInstallDir, \"\")\n\tt.Setenv(update.EnvRepo, \"\")\n\tt.Setenv(update.EnvVersion, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(managed) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallSkipsBrokenEnvInstallPathAndRepairsManagedDefault(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\necho binary-payload >&2\\nexit 127\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\tt.Setenv(update.EnvInstallPath, managed)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after env-path repair: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should point to repaired managed helper, not broken env target:\\n%s\", unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "90ad29152771def0dcaecf3ce80966cdf4fd2133": {"t": 1785755459.6094716, "n": 262197, "h": "b45c08bed294141c", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(managed) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should be created as a symlink, info=%v err=%v\", info, err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingCXPShimWhenManagedTargetCurrent(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.4\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed after creation: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not created for current managed helper: %s\", cxpVersion)\n\t}\n\tif target, err := os.Readlink(cxp); err != nil || target != managed {\n\t\tt.Fatalf(\"managed cxp shim = %q err=%v, want symlink to %s\", target, err, managed)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "1c4fa0284323135c46dc3ea5449d1826ffe06ae9": {"t": 1785755461.792391, "n": 259738, "h": "d600a29f80532438", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(managed) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(_ context.Context, target managedinstall.Target) error {\n\t\twriteVersionedHelperForServiceTest(t, target.Path, \"1.2.4\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "2ec286c1cdca5bc9ffb5c95364aa7d8fe4accc22": {"t": 1785755464.9085898, "n": 259420, "h": "2b36d1236fb56373", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(managed) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\ntype serviceExecutableFileInfoForTest struct {\n\tos.FileInfo\n}\n\nfunc (info serviceExecutableFileInfoForTest) Mode() os.FileMode {\n\treturn info.FileInfo.Mode() | 0o111\n}\n\nfunc serviceExecutableStatForTest(path string) (os.FileInfo, error) {\n\tinfo, err := os.Stat(path)\n\tif err != nil || info.IsDir() {\n\t\treturn info, err\n\t}\n\treturn serviceExecutableFileInfoForTest{FileInfo: info}, nil\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tstat: serviceExecutableStatForTest,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tstat func(string) (os.FileInfo, error)\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevStat := teamsServiceStat\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tif hooks.stat != nil {\n\t\tteamsServiceStat = hooks.stat\n\t}\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceStat = prevStat\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "4600ade9259e10e36934e706b1b7aaf40e063bd7": {"t": 1785755467.440325, "n": 258770, "h": "5247267088db4aec", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(managed) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\tif err := managedinstall.SaveRecord(recordPath, managedinstall.Record{TargetPath: managed}); err != nil {\n\t\tt.Fatalf(\"save install record: %v\", err)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "3e325c9a98f5a163850296ae095e435114fed36f": {"t": 1785755469.567894, "n": 258491, "h": "2914ffd87d0f402d", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(managed) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord := loadManagedInstallRecordForServiceTest(t)\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\nfunc loadManagedInstallRecordForServiceTest(t *testing.T) managedinstall.Record {\n\tt.Helper()\n\trecordPath, err := managedinstall.DefaultRecordPath()\n\tif err != nil {\n\t\tt.Fatalf(\"default install record path: %v\", err)\n\t}\n\trecord, err := managedinstall.LoadRecord(recordPath)\n\tif err != nil {\n\t\tt.Fatalf(\"load install record %s: %v\", recordPath, err)\n\t}\n\treturn record\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "c34d0fc1622f1a99309e99565e29de06d699cee8": {"t": 1785755471.6716766, "n": 258349, "h": "fea8ffbe6ba9e359", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/managedinstall\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesManagedDefaultInsteadOfGoBinExecutable(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tfor _, path := range []string{managed, goBin} {\n\t\tif err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {\n\t\t\tt.Fatalf(\"mkdir %s: %v\", filepath.Dir(path), err)\n\t\t}\n\t\tif err := os.WriteFile(path, []byte(\"stable\"), 0o755); err != nil {\n\t\t\tt.Fatalf(\"write %s: %v\", path, err)\n\t\t}\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(managed) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallMaterializesManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, managed, \"1.2.3\")\n\tif err := os.Symlink(managed, cxp); err != nil {\n\t\tt.Fatalf(\"symlink cxp: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not materialized from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tcxpVersion, err := exec.Command(cxp, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed cxp --version failed: %v\\n%s\", err, cxpVersion)\n\t}\n\tif !strings.Contains(string(cxpVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed cxp shim was not materialized from upgraded go/bin helper: %s\", cxpVersion)\n\t}\n\tif info, err := os.Lstat(cxp); err != nil || info.Mode()&os.ModeSymlink == 0 {\n\t\tt.Fatalf(\"managed cxp shim should remain a symlink after materialization, info=%v err=%v\", info, err)\n\t}\n\trecord, err := managedinstall.LoadRecord(filepath.Join(tmp, \"config\", \"codex-helper\", \"install.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"load install record: %v\", err)\n\t}\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n\tif !slices.Contains(record.Shims, cxp) {\n\t\tt.Fatalf(\"install record shims = %#v, want %s\", record.Shims, cxp)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallCreatesMissingManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tmanagedVersion, err := exec.Command(managed, \"--version\").CombinedOutput()\n\tif err != nil {\n\t\tt.Fatalf(\"managed helper --version failed after creation: %v\\n%s\", err, managedVersion)\n\t}\n\tif !strings.Contains(string(managedVersion), \"1.2.4\") {\n\t\tt.Fatalf(\"managed helper was not created from upgraded go/bin helper: %s\", managedVersion)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(managed)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit did not point at managed helper:\\n%s\", unit)\n\t}\n\tif strings.Contains(unit, goBin) {\n\t\tt.Fatalf(\"unit should not retain go/bin executable %q:\\n%s\", goBin, unit)\n\t}\n\trecord, err := managedinstall.LoadRecord(filepath.Join(tmp, \"config\", \"codex-helper\", \"install.json\"))\n\tif err != nil {\n\t\tt.Fatalf(\"load install record: %v\", err)\n\t}\n\tif record.TargetPath != managed || record.TargetState != string(managedinstall.StateManaged) || record.Version != \"1.2.4\" {\n\t\tt.Fatalf(\"install record = %#v, want managed target %s at 1.2.4\", record, managed)\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotTreatBlockedManagedDefaultAsInstallDir(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(managed, 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir managed target as directory: %v\", err)\n\t}\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(goBin)+\" teams run --owner-stale-after 1m30s --auto-service=false\") {\n\t\tt.Fatalf(\"unit should fall back to current executable when managed target is blocked:\\n%s\", unit)\n\t}\n\tnested := filepath.Join(managed, \"codex-proxy\")\n\tif strings.Contains(unit, nested) {\n\t\tt.Fatalf(\"unit should not treat blocked managed file target as an install directory %q:\\n%s\", nested, unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRepairsCorruptManagedDefaultBeforeRepointingFromGoBin(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"managed target materialization is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\tcxp := filepath.Join(tmp, \".local\", \"bin\", \"cxp\")\n\tgoBin := filepath.Join(tmp, \"go\", \"bin\", \"codex-proxy\")\n\twriteCLIFile(t, managed, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteCLIFile(t, cxp, \"#!/bin/sh\\nexit 42\\n\", 0o755)\n\twriteVersionedHelperForServiceTest(t, goBin, \"1.2.4\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: goBin,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tfor _, path := range []string{managed, cxp} {\n\t\tversionOut, err := exec.Command(path, \"--version\").CombinedOutput()\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"%s --version failed after materialization: %v\\n%s\", path, err, versionOut)\n\t\t}\n\t\tif !strings.Contains(string(versionOut), \"1.2.4\") {\n\t\t\tt.Fatalf(\"%s was not repaired from upgraded go/bin helper: %s\", path, versionOut)\n\t\t}\n\t}\n}\n\nfunc writeVersionedHelperForServiceTest(t *testing.T, path string, version string) {\n\tt.Helper()\n\tscript := \"#!/bin/sh\\nif [ \\\"$1\\\" = \\\"--version\\\" ]; then echo 'codex-proxy version \" + version + \" (test)'; exit 0; fi\\nexit 0\\n\"\n\twriteCLIFile(t, path, script, 0o755)\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\tprevMaterialize := materializeManagedTeamsInstallTarget\n\tmaterializeManagedTeamsInstallTarget = func(context.Context, managedinstall.Target) error {\n\t\tt.Fatal(\"bootstrap --dry-run must not materialize managed install target\")\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tmaterializeManagedTeamsInstallTarget = prevMaterialize\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvInstallPath, \"/tmp/stale-install-path\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twriteVersionedHelperForServiceTest(t, exePath, \"1.2.4\")\n\tmanaged := filepath.Join(tmp, \".local\", \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallPath+\"=\"+managed),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvInstallPath + \"=/tmp/stale-install-path\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "7635a3cf884739b9e0fb8473662fb982f252aa0e": {"t": 1785755473.8930445, "n": 248022, "h": "6d42acdda928fec2", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRAllowTransformersFallback, \"1\")\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\tasrOverrides := teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})\n\tif asrOverrides[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"ASR overrides = %#v, want transformers fallback opt-in preserved\", asrOverrides)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(asrOverrides),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") ||\n\t\tspec.Environment[envTeamsASRAllowTransformersFallback] != \"1\" {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+exePath),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "05cc81d9020b3d30359a8d1cf80cb4d514422991": {"t": 1785755476.3739078, "n": 247712, "h": "4c69104c8e88e976", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/config\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceStartWarnsOnlyForRegisteredUnhealthyProxyDaemon(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy warning: no locally healthy reusable SSH proxy daemon for profile \\\"dev\\\" (p1) (registered=1)\") {\n\t\tt.Fatalf(\"start output missing proxy warning:\\n%s\", got)\n\t}\n\n\trunner.calls = nil\n\tcleanStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: cleanStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start without registered proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); strings.Contains(got, \"Teams Graph proxy warning\") {\n\t\tt.Fatalf(\"start should stay quiet when no proxy daemon is registered:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsTeamsGraphProxyLocalStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tdirectStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(false),\n\t})\n\tcmd := newTeamsServiceCmd(&rootOptions{configPath: directStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with direct proxy: %v\\n%s\", err, out.String())\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams Graph proxy: direct (disabled)\") {\n\t\tt.Fatalf(\"doctor output missing direct proxy status:\\n%s\", got)\n\t}\n\n\tprofile := config.Profile{\n\t\tID: \"p1\",\n\t\tName: \"dev\",\n\t\tHost: \"example.com\",\n\t\tPort: 22,\n\t\tUser: \"alice\",\n\t\tCreatedAt: time.Now(),\n\t}\n\tdeadStore := newTeamsGraphProxyTestStore(t, config.Config{\n\t\tVersion: config.CurrentVersion,\n\t\tProxyEnabled: boolPtr(true),\n\t\tProfiles: []config.Profile{profile},\n\t\tInstances: []config.Instance{{\n\t\t\tID: \"inst-dead\",\n\t\t\tProfileID: profile.ID,\n\t\t\tKind: config.InstanceKindDaemon,\n\t\t\tHTTPPort: 18080,\n\t\t\tDaemonPID: 0,\n\t\t}},\n\t})\n\tcmd = newTeamsServiceCmd(&rootOptions{configPath: deadStore.Path()}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tout.Reset()\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor with dead proxy: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams Graph proxy: SSH profile \\\"dev\\\" (p1)\",\n\t\t\"Teams Graph proxy reusable daemon: registered but no locally healthy daemon (registered=1)\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+exePath),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "f9afbfa41f861eae60545c59043173132ac506b6": {"t": 1785755478.6459756, "n": 243581, "h": "3f08dcb59cb19300", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptCommand(t *testing.T, command string) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t\"codex-helper-teams-uac-\",\n\t\t\"elevated.ps1\",\n\t\t\"stdout.log\",\n\t\t\"stderr.log\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(command, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, command)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\"} {\n\t\tif strings.Contains(command, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should execute a script file instead of inline %q:\\n%s\", forbidden, command)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptForRepair(t *testing.T, script string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\tif expectEnable && !strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should enable repaired tasks:\\n%s\", script)\n\t}\n\tif !expectEnable && strings.Contains(script, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should not enable install-only tasks:\\n%s\", script)\n\t}\n\tif expectStart && !strings.Contains(script, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"elevated script should start repaired tasks:\\n%s\", script)\n\t}\n\tif !expectStart {\n\t\tfor _, forbidden := range []string{\"Start-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\tif strings.Contains(script, forbidden) {\n\t\t\t\tt.Fatalf(\"elevated script should not start tasks before returning to current-user context, found %q:\\n%s\", forbidden, script)\n\t\t\t}\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(script, forbidden) {\n\t\t\tt.Fatalf(\"elevated script must stay current-user least-privilege, found %q:\\n%s\", forbidden, script)\n\t\t}\n\t}\n}\n\nfunc assertTeamsServiceWindowsElevatedScriptFile(t *testing.T, taskDir string, expectEnable bool, expectStart bool) {\n\tt.Helper()\n\tmatches, err := filepath.Glob(filepath.Join(taskDir, \"codex-helper-teams-uac-*\", \"elevated.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob elevated script: %v\", err)\n\t}\n\tif len(matches) != 1 {\n\t\tt.Fatalf(\"elevated script matches = %#v, want exactly one\", matches)\n\t}\n\tdata, err := os.ReadFile(matches[0])\n\tif err != nil {\n\t\tt.Fatalf(\"read elevated script %s: %v\", matches[0], err)\n\t}\n\tassertTeamsServiceWindowsElevatedScriptForRepair(t, string(data), expectEnable, expectStart)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+exePath),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 5 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, elevated repair, and normal start\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tstart := strings.Join(runner.calls[4].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \" + powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t} {\n\t\tif !strings.Contains(start, want) {\n\t\t\tt.Fatalf(\"post-UAC start command missing %q:\\n%s\", want, start)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) || strings.Contains(start, forbidden) {\n\t\t\tt.Fatalf(\"UAC repair/start must stay current-user least-privilege, found %q:\\nelevated=%s\\nstart=%s\", forbidden, elevated, start)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 2 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, false, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsElevatedScriptCapturesInnerErrors(t *testing.T) {\n\tscript := buildTeamsServiceWindowsElevatedScript(\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'; Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"$ErrorActionPreference = 'Stop'\",\n\t\t\"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Start-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\",\n\t\t\"Set-Content -LiteralPath $stderrLog -Value $message -Encoding UTF8\",\n\t\t\"exit 1\",\n\t\t\"exit 0\",\n\t} {\n\t\tif !strings.Contains(script, want) {\n\t\t\tt.Fatalf(\"elevated script missing %q:\\n%s\", want, script)\n\t\t}\n\t}\n\n\tlauncher := buildTeamsServiceWindowsElevatedScriptCommand(\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t)\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"-File\",\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\elevated.ps1`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stdout.log`,\n\t\t`C:\\Users\\alice\\AppData\\Local\\Temp\\codex-uac\\stderr.log`,\n\t\t\"Get-Content -LiteralPath $uacLog -Raw\",\n\t\t\"elevated Teams service bootstrap failed with exit code\",\n\t} {\n\t\tif !strings.Contains(launcher, want) {\n\t\t\tt.Fatalf(\"elevated launcher missing %q:\\n%s\", want, launcher)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\"Register-ScheduledTask\", \"Start-ScheduledTask -TaskName\"} {\n\t\tif strings.Contains(launcher, forbidden) {\n\t\t\tt.Fatalf(\"elevated launcher should not inline inner command %q:\\n%s\", forbidden, launcher)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t\tonRun: func(index int, _ string, _ []string) {\n\t\t\tif index == 3 {\n\t\t\t\tassertTeamsServiceWindowsElevatedScriptFile(t, taskDir, true, false)\n\t\t\t}\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tassertTeamsServiceWindowsElevatedScriptCommand(t, elevated)\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "b8da22e6fd08083cb4a9fdbb80f388cc051f5c2a": {"t": 1785755482.1198373, "n": 238785, "h": "4860a4de98debe05", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsTeamsServiceModeWithoutReading(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\n\tvar out strings.Builder\n\tif confirmTeamsServiceUACPrompt(strings.NewReader(\"yes\\n\"), &out, false) {\n\t\tt.Fatal(\"Teams service mode must not accept an interactive UAC confirmation\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestConfirmTeamsServiceUACPromptRejectsNonTerminalStdinWithoutBlocking(t *testing.T) {\n\tlockCLITestHooks(t)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\n\treader, writer, err := os.Pipe()\n\tif err != nil {\n\t\tt.Fatalf(\"pipe: %v\", err)\n\t}\n\tprevStdin := os.Stdin\n\tos.Stdin = reader\n\tt.Cleanup(func() {\n\t\tos.Stdin = prevStdin\n\t\t_ = reader.Close()\n\t\t_ = writer.Close()\n\t})\n\n\tdone := make(chan bool, 1)\n\tvar out strings.Builder\n\tgo func() {\n\t\tdone <- confirmTeamsServiceUACPrompt(nil, &out, false)\n\t}()\n\tselect {\n\tcase confirmed := <-done:\n\t\tif confirmed {\n\t\t\tt.Fatal(\"non-terminal stdin must not confirm UAC\")\n\t\t}\n\tcase <-time.After(time.Second):\n\t\tt.Fatal(\"UAC confirmation blocked on non-terminal stdin\")\n\t}\n\tif !strings.Contains(out.String(), \"stdin is non-interactive\") {\n\t\tt.Fatalf(\"expected non-interactive explanation, got:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+exePath),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesModelProfileProviderKeys(t *testing.T) {\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-test\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\n\tenv := teamsServiceEnvironment()\n\tif env[\"MIMO_API_KEY\"] != \"sk-mimo-test\" {\n\t\tt.Fatalf(\"MIMO_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"DEEPSEEK_API_KEY\"] != \"sk-deepseek-test\" {\n\t\tt.Fatalf(\"DEEPSEEK_API_KEY not preserved for Teams service model profiles: %#v\", env)\n\t}\n\tif env[\"UNRELATED_MODEL_KEY\"] != \"\" {\n\t\tt.Fatalf(\"unexpected non-allowlisted model key preserved: %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceInstallRendersModelProfileProviderKeysForBackgroundService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", filepath.Join(tmp, \"cxp-config.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"thirdparty-service\")\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-deepseek-background-test\")\n\tt.Setenv(\"MIMO_API_KEY\", \"sk-mimo-background-test\")\n\tt.Setenv(\"QWEN_API_KEY\", \"sk-qwen-background-test\")\n\tt.Setenv(\"UNRELATED_MODEL_KEY\", \"sk-ignored\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + systemdQuoteArg(registryPath),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_CONFIG=\"+filepath.Join(tmp, \"cxp-config.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=thirdparty-service\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"DEEPSEEK_API_KEY=sk-deepseek-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"MIMO_API_KEY=sk-mimo-background-test\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"QWEN_API_KEY=sk-qwen-background-test\"),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing model-profile service entry %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, \"UNRELATED_MODEL_KEY\") || strings.Contains(unit, \"sk-ignored\") {\n\t\tt.Fatalf(\"unit preserved non-allowlisted model key:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceWindowsStartTasksRefreshesTaskState(t *testing.T) {\n\tgot := teamsServiceWindowsStartTasksPowerShell()\n\trequireSubstringsInOrder(t, got,\n\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Start-CodexHelperScheduledTaskIfStopped \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t)\n\tif strings.Contains(got, \"$watchdogTask.State -ne 'Running') { Start-ScheduledTask\") {\n\t\tt.Fatalf(\"watchdog start must not use stale task state:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLStartTasksRefreshTaskState(t *testing.T) {\n\tfor name, got := range map[string]string{\n\t\t\"start\": teamsServiceWSLStartTaskAndVerifyPowerShell(),\n\t\t\"start_if_stopped\": teamsServiceWSLStartTaskIfStoppedAndVerifyPowerShell(),\n\t} {\n\t\tt.Run(name, func(t *testing.T) {\n\t\t\trequireSubstringsInOrder(t, got,\n\t\t\t\t\"function Test-CodexHelperScheduledTaskRunning\",\n\t\t\t\t\"function Start-CodexHelperScheduledTaskIfStopped\",\n\t\t\t\t\"try { Start-ScheduledTask -TaskName $taskName -ErrorAction Stop | Out-Null } catch\",\n\t\t\t\t\"if (-not (Test-CodexHelperScheduledTaskRunning $taskName 10)) { throw }\",\n\t\t\t\t\"Start-CodexHelperScheduledTaskIfStopped $taskName\",\n\t\t\t\t\"Teams WSL Scheduled Task did not stay running after start\",\n\t\t\t)\n\t\t\tif strings.Contains(got, \"$task.State -ne 'Running') { Start-ScheduledTask -TaskName $taskName\") {\n\t\t\t\tt.Fatalf(\"WSL start must not use stale task state:\\n%s\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t} {\n\t\tif !strings.Contains(elevated, want) {\n\t\t\tt.Fatalf(\"elevated command missing %q:\\n%s\", want, elevated)\n\t\t}\n\t}\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t)\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) {\n\t\t\tt.Fatalf(\"elevated command must stay current-user least-privilege, found %q:\\n%s\", forbidden, elevated)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif strings.Contains(elevated, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap --no-start elevated repair must not start tasks:\\n%s\", elevated)\n\t}\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t)\n\tfor _, forbidden := range []string{\"Enable-ScheduledTask\", \"Start-ScheduledTask\"} {\n\t\tif strings.Contains(elevated, forbidden) {\n\t\t\tt.Fatalf(\"install elevated repair must not run %s:\\n%s\", forbidden, elevated)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t)\n\tif strings.Contains(elevated, \"Start-ScheduledTask\") || strings.Contains(elevated, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"install cleanup UAC repair must not enable or start tasks:\\n%s\", elevated)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif strings.Contains(elevated, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"pending activation elevated repair must not start old tasks:\\n%s\", elevated)\n\t}\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-CodexHelperScheduledTaskIfStopped\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-CodexHelperScheduledTaskIfStopped\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, \"-WindowStyle Hidden -Command\") {\n\t\tt.Fatalf(\"powershell args should hide helper status probes, got %q\", joined)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "9705dcb7c28950ed4c9a0b71bb147763b6423cac": {"t": 1785755528.7722607, "n": 232647, "h": "6eb7a11d3abe54e1", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaBinary, filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\"))\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\tt.Setenv(envTeamsASRLlamaDevice, \"cpu\")\n\tt.Setenv(envTeamsASRFFmpeg, filepath.Join(tmp, \"bin\", \"ffmpeg\"))\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRBackend+\"=llama\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaBinary+\"=\"+filepath.Join(tmp, \"bin\", \"llama-mtmd-cli\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaModel+\"=\"+filepath.Join(tmp, \"models\", \"qwen.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaMMProj+\"=\"+filepath.Join(tmp, \"models\", \"mmproj.gguf\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRLlamaDevice+\"=cpu\"),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRFFmpeg+\"=\"+filepath.Join(tmp, \"bin\", \"ffmpeg\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\tt.Setenv(envTeamsASRBackend, \"llama\")\n\tt.Setenv(envTeamsASRLlamaModel, filepath.Join(tmp, \"models\", \"qwen.gguf\"))\n\tt.Setenv(envTeamsASRLlamaMMProj, filepath.Join(tmp, \"models\", \"mmproj.gguf\"))\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n\tif spec.Environment[envTeamsASRBackend] != \"llama\" ||\n\t\tspec.Environment[envTeamsASRLlamaModel] != filepath.Join(tmp, \"models\", \"qwen.gguf\") ||\n\t\tspec.Environment[envTeamsASRLlamaMMProj] != filepath.Join(tmp, \"models\", \"mmproj.gguf\") {\n\t\tt.Fatalf(\"managed ASR env = %#v\", spec.Environment)\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+exePath),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t} {\n\t\tif !strings.Contains(elevated, want) {\n\t\t\tt.Fatalf(\"elevated command missing %q:\\n%s\", want, elevated)\n\t\t}\n\t}\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t)\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) {\n\t\t\tt.Fatalf(\"elevated command must stay current-user least-privilege, found %q:\\n%s\", forbidden, elevated)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif strings.Contains(elevated, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap --no-start elevated repair must not start tasks:\\n%s\", elevated)\n\t}\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t)\n\tfor _, forbidden := range []string{\"Enable-ScheduledTask\", \"Start-ScheduledTask\"} {\n\t\tif strings.Contains(elevated, forbidden) {\n\t\t\tt.Fatalf(\"install elevated repair must not run %s:\\n%s\", forbidden, elevated)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t)\n\tif strings.Contains(elevated, \"Start-ScheduledTask\") || strings.Contains(elevated, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"install cleanup UAC repair must not enable or start tasks:\\n%s\", elevated)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-ScheduledTask\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif strings.Contains(elevated, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"pending activation elevated repair must not start old tasks:\\n%s\", elevated)\n\t}\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-ScheduledTask\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-ScheduledTask\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-ScheduledTask\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}, "72f2603a9b2525e49146020bf6abc2d6f9ebae67": {"t": 1785755530.821467, "n": 231231, "h": "c2de75984f70324c", "c": "package cli\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"os/exec\"\n\t\"path/filepath\"\n\t\"reflect\"\n\t\"runtime\"\n\t\"slices\"\n\t\"strconv\"\n\t\"strings\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/gofrs/flock\"\n\n\t\"github.com/baaaaaaaka/codex-helper/internal/beacon\"\n\tteamsstore \"github.com/baaaaaaaka/codex-helper/internal/teams/store\"\n\t\"github.com/baaaaaaaka/codex-helper/internal/update\"\n)\n\nfunc isolateTeamsUserDirsForTest(t *testing.T, tmp string) (string, string) {\n\tt.Helper()\n\tt.Setenv(\"HOME\", tmp)\n\tt.Setenv(\"USERPROFILE\", tmp)\n\tt.Setenv(\"APPDATA\", filepath.Join(tmp, \"AppData\", \"Roaming\"))\n\tt.Setenv(\"LOCALAPPDATA\", filepath.Join(tmp, \"AppData\", \"Local\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(tmp, \"config\"))\n\tt.Setenv(\"XDG_CACHE_HOME\", filepath.Join(tmp, \"cache\"))\n\tt.Setenv(\"XDG_RUNTIME_DIR\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n\tconfigBase, err := os.UserConfigDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserConfigDir: %v\", err)\n\t}\n\tcacheBase, err := os.UserCacheDir()\n\tif err != nil {\n\t\tt.Fatalf(\"os.UserCacheDir: %v\", err)\n\t}\n\treturn configBase, cacheBase\n}\n\nfunc setTeamsAuthIDsForCLITest(t *testing.T) {\n\tt.Helper()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_TENANT_ID\", \"tenant\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_ID\", \"chat-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_CLIENT_ID\", \"read-client\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FILE_WRITE_CLIENT_ID\", \"file-client\")\n}\n\nfunc teamsServiceTestAbsPath(t *testing.T, path string) string {\n\tt.Helper()\n\tout, err := filepath.Abs(path)\n\tif err != nil {\n\t\tt.Fatalf(\"filepath.Abs(%q): %v\", path, err)\n\t}\n\treturn out\n}\n\nfunc teamsServiceTestRegistryPath(cwd string, registryPath string) string {\n\tregistryPath = strings.TrimSpace(registryPath)\n\tif filepath.IsAbs(registryPath) {\n\t\treturn registryPath\n\t}\n\treturn filepath.Join(cwd, registryPath)\n}\n\nfunc TestTeamsServiceInstallWritesSystemdUserUnitWithoutEnabling(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tdata, err := os.ReadFile(unitPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"[Unit]\",\n\t\t\"Description=Codex Helper Teams bridge\",\n\t\t\"WorkingDirectory=\" + strconv.Quote(cwd),\n\t\t\"ExecStart=\" + systemdQuoteArg(exePath) + \" teams run --owner-stale-after 1m30s --auto-service=false --registry \" + strconv.Quote(registryPath),\n\t\t\"Restart=on-failure\",\n\t\t\"RestartSec=10s\",\n\t\t\"Environment=NO_COLOR=1\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+exePath),\n\t\t\"Environment=CODEX_HELPER_TEAMS_SERVICE=1\",\n\t\t\"[Install]\",\n\t\t\"WantedBy=default.target\",\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tfor _, call := range runner.calls {\n\t\tjoined := strings.Join(call.args, \" \")\n\t\tif strings.Contains(joined, \"enable\") || strings.Contains(joined, \"start\") {\n\t\t\tt.Fatalf(\"install should not enable or start service, calls=%#v\", runner.calls)\n\t\t}\n\t}\n\tif !strings.Contains(out.String(), \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output should state no auto enable/start:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackToLocalSupervisorWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFailsClosedOnUnknownSystemdProbeError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user availability\") {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform err = %v, want fail-closed systemd probe error\", err)\n\t}\n}\n\nfunc TestTeamsServiceSystemdUserUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No such file or directory\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: org.freedesktop.DBus.Error.NameHasNoOwner\",\n\t\t\"systemctl --user show-environment failed: exit status 1: The name org.freedesktop.systemd1 was not provided by any .service files\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Unit dbus-org.freedesktop.systemd1.service not found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: No medium found\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Host is down\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Transport endpoint is not connected\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: $XDG_RUNTIME_DIR not set\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t\t\"systemctl --user show-environment failed: signal: killed\",\n\t\t\"systemctl --user show-environment failed: exit status 1: System has not been booted with systemd as init system\",\n\t\t\"systemctl --user show-environment failed: exec: \\\"systemctl\\\": executable file not found in $PATH\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"systemd unavailable sample was not classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: invalid option --definitely-not-real\",\n\t\t\"systemctl --user show-environment failed: exit status 1: malformed unit file\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif teamsServiceSystemdUserUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown systemd sample was incorrectly classified for fallback:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoFallsBackForKnownSystemdProbeFailures(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\tsamples := []string{\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\",\n\t\t\"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\",\n\t\t\"systemctl --user show-environment failed: context deadline exceeded\",\n\t}\n\tfor _, sample := range samples {\n\t\tt.Run(sample, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(sample),\n\t\t\t})\n\n\t\t\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t\t\t}\n\t\t\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\t\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoUsesSystemdWhenOnlyDisabledLocalConfigExists(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoStickyCheckDoesNotCreateLocalSupervisorRuntimeDir(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n\trunDir := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\")\n\tif _, err := os.Stat(runDir); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"sticky check created runtime dir %s: %v\", runDir, err)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tpath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(path, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write enabled local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceLinuxAutoIgnoresStaleLocalSupervisorStatusIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\tidentityErr := errors.New(\"not the local supervisor\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn identityErr\n\t\t},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: false,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write disabled local config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write stale local status: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitSystemdDoesNotFallBackToLocalSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitLocalSupervisorOptIn(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"local-supervisor\")\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLHonorsLinuxLocalSupervisorWhenWSLModeUnset(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tsystemdAvailable := true\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLSpecificModeWinsOverLinuxMode(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"systemd\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"local-supervisor\")\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceBackendForCurrentPlatform error: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"systemd-user\" {\n\t\tt.Fatalf(\"backend ID = %q, want systemd-user\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLAutoKeepsEnabledLocalSupervisorConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"local-supervisor\" {\n\t\tt.Fatalf(\"backend ID = %q, want local-supervisor sticky in WSL auto\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLExplicitWindowsTaskOverridesLocalSupervisorSticky(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"windows-task\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local config: %v\", err)\n\t}\n\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tif got := backend.ID(); got != \"wsl-windows-task-scheduler\" {\n\t\tt.Fatalf(\"backend ID = %q, want explicit Windows Task backend\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidSpecificModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\", \"typo\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported WSL backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_WSL_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want WSL backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceWSLInvalidLinuxModeFailsClosed(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"typo\")\n\n\t_, err := teamsServiceBackendForCurrentPlatform()\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceBackendForCurrentPlatform error = nil, want unsupported Linux backend error\")\n\t}\n\tif got := err.Error(); !strings.Contains(got, \"unsupported WSL Teams service backend\") ||\n\t\t!strings.Contains(got, \"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\") {\n\t\tt.Fatalf(\"error = %v, want Linux backend env error\", err)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesLocalSupervisorConfigWhenSystemdUserUnavailable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\tregistryPath := filepath.Join(tmp, \"teams-registry.json\")\n\tsystemdUnavailable := false\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdUnitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: systemdUnitDir,\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t})\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif err := os.MkdirAll(systemdUnitDir, 0o700); err != nil {\n\t\t\tt.Fatalf(\"mkdir systemd unit dir: %v\", err)\n\t\t}\n\t\tif err := os.WriteFile(filepath.Join(systemdUnitDir, name), []byte(\"stale systemd unit\"), 0o600); err != nil {\n\t\t\tt.Fatalf(\"write stale systemd unit: %v\", err)\n\t\t}\n\t}\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tconfigPath := filepath.Join(configBase, \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read local supervisor config: %v\", err)\n\t}\n\tvar cfg teamsServiceLocalSupervisorConfig\n\tif err := json.Unmarshal(data, &cfg); err != nil {\n\t\tt.Fatalf(\"unmarshal local supervisor config: %v\\n%s\", err, data)\n\t}\n\tif cfg.Enabled {\n\t\tt.Fatal(\"install should write local supervisor config disabled\")\n\t}\n\tif cfg.Spec.Executable != exePath {\n\t\tt.Fatalf(\"config executable = %q, want %q\", cfg.Spec.Executable, exePath)\n\t}\n\tif cfg.Spec.WorkingDir != cwd {\n\t\tt.Fatalf(\"config working dir = %q, want %q\", cfg.Spec.WorkingDir, cwd)\n\t}\n\tif cfg.Spec.RegistryPath != registryPath {\n\t\tt.Fatalf(\"config registry path = %q, want %q\", cfg.Spec.RegistryPath, registryPath)\n\t}\n\tif cfg.Spec.Environment[\"CODEX_HELPER_TEAMS_SERVICE\"] != \"1\" {\n\t\tt.Fatalf(\"config environment missing Teams service marker: %#v\", cfg.Spec.Environment)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"local supervisor install should not call systemctl, calls=%#v\", runner.calls)\n\t}\n\tfor _, name := range []string{teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName} {\n\t\tif _, err := os.Stat(filepath.Join(systemdUnitDir, name)); err != nil {\n\t\t\tt.Fatalf(\"install should not retire stale systemd unit %s before start, stat err=%v\", name, err)\n\t\t}\n\t}\n\tif got := out.String(); !strings.Contains(got, configPath) || !strings.Contains(got, \"not enabled or started automatically\") {\n\t\tt.Fatalf(\"install output missing local config path or no-start text:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdFailureBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tretireErr := errors.New(\"systemd stop failed\")\n\trunner := &recordingTeamsServiceRunner{err: retireErr}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif strings.TrimSpace(path) == \"\" {\n\t\tt.Fatalf(\"install path is empty\")\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want systemd retire failure\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd retire failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdUnavailableAllowsStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6124\n\trunner := &recordingTeamsServiceRunner{\n\t\toutput: []byte(\"Failed to connect to bus: Connection refused\\n\"),\n\t\terr: errors.New(\"exit status 1\"),\n\t}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7124,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start should allow local-supervisor when systemd disappears during retire: %v\", err)\n\t}\n\tif !started {\n\t\tt.Fatal(\"local supervisor did not start after systemd became unavailable during retire\")\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"stop\") || !teamsServiceCallSeen(runner.calls, \"disable\") {\n\t\tt.Fatalf(\"retire calls = %#v, want stop and disable attempted before fallback start\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRetireSystemdTimeoutBlocksStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{err: context.DeadlineExceeded}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install should only write local supervisor config before retiring systemd: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err == nil || !strings.Contains(err.Error(), \"stop old systemd\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd stop timeout\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after systemd stop timeout\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdProbeUnknownBlocksFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailableErr: errors.New(\"systemctl --user show-environment failed: exit status 1\"),\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"verify systemd --user is unavailable\") {\n\t\tt.Fatalf(\"start err = %v, want fail-closed systemd probe error\", err)\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after unknown systemd probe failure\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorSystemdUnavailableProbeAllowsFallbackStart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\terr string\n\t}{\n\t\t{name: \"dbus permission denied\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get D-Bus connection: Operation not permitted\"},\n\t\t{name: \"environment probe exited\", err: \"systemctl --user show-environment failed: exit status 1: Failed to get environment: Process org.freedesktop.systemd1 exited with status 1\"},\n\t\t{name: \"bus connection refused\", err: \"systemctl --user show-environment failed: exit status 1: Failed to connect to bus: Connection refused\"},\n\t\t{name: \"dbus cannot autolaunch\", err: \"systemctl --user show-environment failed: exit status 1: Cannot autolaunch D-Bus without X11 $DISPLAY\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\ttmp := t.TempDir()\n\t\t\tisolateTeamsUserDirsForTest(t, tmp)\n\t\t\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\t\t\tstartedPID := 6123\n\t\t\tstarted := false\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"linux\",\n\t\t\t\texe: exePath,\n\t\t\t\tcwd: tmp,\n\t\t\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tsystemdUserAvailableErr: errors.New(tc.err),\n\t\t\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\t\t\tstarted = true\n\t\t\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\t\t\tConfigPath: configPath,\n\t\t\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\t\t\tSupervisorPGID: 7123,\n\t\t\t\t\t\tState: \"running\",\n\t\t\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\t\t\treturn nil\n\t\t\t\t},\n\t\t\t})\n\t\t\tprevAlive := teamsLocalSupervisorProcessAlive\n\t\t\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\t\t\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\t\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t\t\t}\n\t\t\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\t\t\tt.Fatalf(\"start should allow confirmed unavailable systemd probe: %v\", err)\n\t\t\t}\n\t\t\tif !started {\n\t\t\t\tt.Fatal(\"local supervisor did not start after confirmed unavailable systemd probe\")\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartPreflightFailureDoesNotRetireSystemd(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\trunner := &recordingTeamsServiceRunner{}\n\tsystemdAvailable := true\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tbadCacheHome := filepath.Join(tmp, \"cache-file\")\n\tif err := os.WriteFile(badCacheHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad cache home: %v\", err)\n\t}\n\tbadHome := filepath.Join(tmp, \"home-file\")\n\tif err := os.WriteFile(badHome, []byte(\"not a directory\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write bad home: %v\", err)\n\t}\n\tt.Setenv(\"HOME\", badHome)\n\tt.Setenv(\"XDG_CACHE_HOME\", badCacheHome)\n\tt.Setenv(\"LOCALAPPDATA\", badCacheHome)\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil {\n\t\tt.Fatal(\"start err = nil, want log preflight failure\")\n\t}\n\tif started {\n\t\tt.Fatal(\"local supervisor started after log preflight failure\")\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"start should not retire systemd before local preflight succeeds, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRetiresExistingLinuxBridgeProcess(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstartedPID := 6201\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7201,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) {\n\t\treturn []teamsServiceLocalProcess{\n\t\t\t{PID: 6301, Args: []string{exePath, \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, Env: map[string]string{}},\n\t\t\t{PID: 6302, Args: []string{exePath, \"teams\", \"service\", \"watchdog\", \"--loop\"}, Env: map[string]string{}},\n\t\t}, nil\n\t}\n\tteamsServiceTerminateLocalProcess = func(pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: filepath.Join(tmp, \"registry.json\"),\n\t}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{6301}) {\n\t\tt.Fatalf(\"terminated = %#v, want only matching direct teams run\", terminated)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorWSLStartRetiresScheduledTasks(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tstartedPID := 6401\n\tvar events []string\n\trunner := teamsServiceCommandRunnerFunc(func(_ context.Context, name string, args ...string) ([]byte, error) {\n\t\tevents = append(events, name+\" \"+strings.Join(args, \" \"))\n\t\treturn nil, nil\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tevents = append(events, \"start-local\")\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: 7401,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == startedPID }\n\tt.Cleanup(func() { teamsLocalSupervisorProcessAlive = prevAlive })\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor config: %v\", err)\n\t}\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\"); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif len(events) < 2 || !strings.Contains(events[0], \"Disable-ScheduledTask\") || events[1] != \"start-local\" {\n\t\tt.Fatalf(\"events = %#v, want WSL Scheduled Task retire before local start\", events)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorEnableStartStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tvar startedConfigPath string\n\tvar startedLogPath string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, logPath string, _ teamsServiceSpec) (int, error) {\n\t\t\tstartedConfigPath = configPath\n\t\t\tstartedLogPath = logPath\n\t\t\treturn os.Getpid(), writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tLogPath: logPath,\n\t\t\t\tSupervisorPID: os.Getpid(),\n\t\t\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"enable\"})\n\tvar enableOut bytes.Buffer\n\tcmd.SetOut(&enableOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"enable local supervisor: %v\", err)\n\t}\n\tif !strings.Contains(enableOut.String(), \"does not provide machine/container reboot autostart\") {\n\t\tt.Fatalf(\"enable output missing local fallback limitation:\\n%s\", enableOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\tvar startOut bytes.Buffer\n\tcmd.SetOut(&startOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"start local supervisor: %v\", err)\n\t}\n\tif startedConfigPath == \"\" || startedLogPath == \"\" {\n\t\tt.Fatalf(\"start hook did not receive config/log paths: config=%q log=%q\", startedConfigPath, startedLogPath)\n\t}\n\tif !strings.Contains(startOut.String(), \"Started Teams service: Codex Helper Teams local supervisor\") {\n\t\tt.Fatalf(\"start output missing backend name:\\n%s\", startOut.String())\n\t}\n\n\tcmd = newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"status\"})\n\tvar statusOut bytes.Buffer\n\tcmd.SetOut(&statusOut)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"status local supervisor: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"Active: true\",\n\t\t\"SupervisorPID:\",\n\t\t\"Autostart: not guaranteed after machine/container reboot\",\n\t\tfilepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName),\n\t} {\n\t\tif !strings.Contains(statusOut.String(), want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, statusOut.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairRestartsActiveForNewSpec(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tstops := 0\n\tstarts := 0\n\tactivePID := 1111\n\tactivePGID := 2111\n\tstartedPID := 2222\n\tstartedPGID := 3222\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, teamsServiceRepairOptions{Enable: true, Start: true})\n\tif err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif path != configPath {\n\t\tt.Fatalf(\"repair path = %q, want %q\", path, configPath)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"repair stops=%d starts=%d, want one restart\", stops, starts)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read config: %v\", err)\n\t}\n\tif !cfg.Enabled || cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config after repair = %#v, want enabled new spec\", cfg)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRepairStopsChildWithOldSpecBeforeWritingNewConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\tactivePID := 3101\n\tactivePGID := 4101\n\tchildPID := 3102\n\tchildPGID := 4102\n\tstartedPID := 3201\n\tstartedPGID := 4201\n\tvar verifiedChildRegistry string\n\tvar terminated []int\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalVerifyChildIdentity: func(pid int, spec teamsServiceSpec) error {\n\t\t\tif pid != childPID {\n\t\t\t\tt.Fatalf(\"verified child pid = %d, want %d\", pid, childPID)\n\t\t\t}\n\t\t\tverifiedChildRegistry = spec.RegistryPath\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == childPID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase childPID:\n\t\t\treturn childPGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(_ int, pid int, _ time.Duration) error {\n\t\tterminated = append(terminated, pid)\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write old config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tChildPID: childPID,\n\t\tChildPGID: childPGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Repair(context.Background(), teamsServiceSpec{\n\t\tExecutable: exePath,\n\t\tWorkingDir: tmp,\n\t\tRegistryPath: \"new\",\n\t}, teamsServiceRepairOptions{Enable: true, Start: true}); err != nil {\n\t\tt.Fatalf(\"repair local supervisor: %v\", err)\n\t}\n\tif verifiedChildRegistry != \"old\" {\n\t\tt.Fatalf(\"verified child registry = %q, want old config before rewrite\", verifiedChildRegistry)\n\t}\n\tif !reflect.DeepEqual(terminated, []int{childPID, activePID}) {\n\t\tt.Fatalf(\"terminated pids = %#v, want child then supervisor\", terminated)\n\t}\n\tcfg, err := readTeamsServiceLocalSupervisorConfig(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read new config: %v\", err)\n\t}\n\tif cfg.Spec.RegistryPath != \"new\" {\n\t\tt.Fatalf(\"config registry = %q, want new\", cfg.Spec.RegistryPath)\n\t}\n}\n\nfunc TestTeamsServiceHelperUpgradeRestartsActiveLocalSupervisorAfterConfigRefresh(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdAvailable := true\n\tactivePID := 8101\n\tactivePGID := 9101\n\tstartedPID := 8102\n\tstartedPGID := 9102\n\tstops := 0\n\tstarts := 0\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdAvailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t\tlocalStartDetached: func(_ context.Context, configPath string, _ string, _ teamsServiceSpec) (int, error) {\n\t\t\tstarts++\n\t\t\treturn startedPID, writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\t\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\t\t\tConfigPath: configPath,\n\t\t\t\tSupervisorPID: startedPID,\n\t\t\t\tSupervisorPGID: startedPGID,\n\t\t\t\tState: \"running\",\n\t\t\t\tUpdatedAt: time.Now(),\n\t\t\t})\n\t\t},\n\t})\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == activePID || pid == startedPID\n\t}\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tswitch pid {\n\t\tcase activePID:\n\t\t\treturn activePGID, nil\n\t\tcase startedPID:\n\t\t\treturn startedPGID, nil\n\t\tdefault:\n\t\t\treturn 0, fmt.Errorf(\"unexpected pid %d\", pid)\n\t\t}\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 999 }\n\tteamsLocalSupervisorTerminateProcessGroup = func(int, int, time.Duration) error {\n\t\tstops++\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{Executable: exePath, WorkingDir: tmp, RegistryPath: \"old\"},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: activePID,\n\t\tSupervisorPGID: activePGID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\tif _, err := refreshTeamsServiceForHelperUpgrade(context.Background(), stringPtr(filepath.Join(tmp, \"registry.json\")), strings.NewReader(\"\"), io.Discard); err != nil {\n\t\tt.Fatalf(\"refresh Teams service for helper upgrade: %v\", err)\n\t}\n\tif stops != 1 || starts != 1 {\n\t\tt.Fatalf(\"upgrade refresh stops=%d starts=%d, want active local supervisor restarted\", stops, starts)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for a PID that failed local supervisor identity verification\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorActiveRejectsCurrentStatusWithoutHeartbeat(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{Executable: filepath.Join(tmp, \"bin\", \"codex-proxy\"), WorkingDir: tmp},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\tactive, err := (teamsServiceLocalSupervisorBackend{}).Active(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"active error: %v\", err)\n\t}\n\tif active {\n\t\tt.Fatal(\"Active returned true for current-version status without heartbeat UpdatedAt\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRuntimeDirIgnoresXDGRuntimeDir(t *testing.T) {\n\ttmp := t.TempDir()\n\tconfigBase, _ := isolateTeamsUserDirsForTest(t, tmp)\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(tmp, \"runtime\"))\n\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\twant := filepath.Join(configBase, \"codex-helper\", \"teams\", \"run\", \"local-supervisor\", teamsServiceLocalSupervisorStatusName)\n\tif statusPath != want {\n\t\tt.Fatalf(\"status path = %q, want stable config-root path %q\", statusPath, want)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartRequiresReadyStatus(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tpath, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp})\n\tif err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"did not report ready\") {\n\t\tt.Fatalf(\"start err = %v, want ready timeout\", err)\n\t}\n\tif path == \"\" {\n\t\tt.Fatal(\"install path should not be empty\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorReleaseFailureTerminatesStartedSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\treleaseErr := errors.New(\"release failed\")\n\tconfigPath := filepath.Join(t.TempDir(), \"local-supervisor.json\")\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevProcessGroupID := teamsLocalSupervisorProcessGroupID\n\tprevCurrentProcessGroupID := teamsLocalSupervisorCurrentProcessGroupID\n\tprevTerminate := teamsLocalSupervisorTerminateProcessGroup\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != 8123 || gotConfigPath != configPath {\n\t\t\tt.Fatalf(\"verify pid/config = %d %q, want 8123 %q\", pid, gotConfigPath, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool { return pid == 8123 }\n\tteamsLocalSupervisorProcessGroupID = func(pid int) (int, error) {\n\t\tif pid != 8123 {\n\t\t\tt.Fatalf(\"process group pid = %d, want 8123\", pid)\n\t\t}\n\t\treturn 9123, nil\n\t}\n\tteamsLocalSupervisorCurrentProcessGroupID = func() int { return 100 }\n\tvar terminatedPGID int\n\tvar terminatedPID int\n\tteamsLocalSupervisorTerminateProcessGroup = func(pgid int, leaderPID int, _ time.Duration) error {\n\t\tterminatedPGID = pgid\n\t\tterminatedPID = leaderPID\n\t\treturn nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorProcessGroupID = prevProcessGroupID\n\t\tteamsLocalSupervisorCurrentProcessGroupID = prevCurrentProcessGroupID\n\t\tteamsLocalSupervisorTerminateProcessGroup = prevTerminate\n\t})\n\twaited := false\n\terr := handleTeamsServiceLocalSupervisorReleaseFailure(8123, configPath, releaseErr, func() error {\n\t\twaited = true\n\t\treturn nil\n\t})\n\tif !errors.Is(err, releaseErr) {\n\t\tt.Fatalf(\"release failure err = %v, want %v\", err, releaseErr)\n\t}\n\tif terminatedPGID != 9123 || terminatedPID != 8123 {\n\t\tt.Fatalf(\"terminated pgid/pid = %d/%d, want 9123/8123\", terminatedPGID, terminatedPID)\n\t}\n\tif !waited {\n\t\tt.Fatal(\"release failure cleanup did not wait after successful termination\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsIdentityMismatch(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopRejectsStaleSupervisorPGID(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err == nil || !strings.Contains(err.Error(), \"stale local supervisor process group\") {\n\t\tt.Fatalf(\"stop err = %v, want stale PGID rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorUninstallPropagatesStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Uninstall(context.Background())\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"uninstall err = %v, want %v\", err, verifyErr)\n\t}\n\tif _, err := os.Stat(configPath); err != nil {\n\t\tt.Fatalf(\"config should remain after failed stop, stat err=%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRestartDoesNotStartAfterStopError(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"config path: %v\", err)\n\t}\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"identity mismatch\")\n\tstarted := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyProcessIdentity: func(int, string) error {\n\t\t\treturn verifyErr\n\t\t},\n\t\tlocalStartDetached: func(context.Context, string, string, teamsServiceSpec) (int, error) {\n\t\t\tstarted = true\n\t\t\treturn os.Getpid(), nil\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: os.Getpid(),\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"restart\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"restart err = %v, want %v\", err, verifyErr)\n\t}\n\tif started {\n\t\tt.Fatal(\"restart started local supervisor after stop failed\")\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopDeadSupervisorRejectsUnverifiedChild(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tconfigPath := filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName)\n\tsystemdUnavailable := false\n\tverifyErr := errors.New(\"child identity mismatch\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalVerifyChildIdentity: func(int, teamsServiceSpec) error {\n\t\t\treturn verifyErr\n\t\t},\n\t})\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: 99999999,\n\t\tSupervisorPGID: 99999999,\n\t\tChildPID: os.Getpid(),\n\t\tChildPGID: teamsLocalSupervisorCurrentProcessGroupID() + 100000,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write status: %v\", err)\n\t}\n\t_, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, verifyErr) {\n\t\tt.Fatalf(\"stop err = %v, want %v\", err, verifyErr)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusFailsClosedWhenLockHeld(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif !errors.Is(err, errTeamsServiceLocalSupervisorStatusMalformed) {\n\t\tt.Fatalf(\"stop err = %v, want malformed status error\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStopMalformedStatusWithoutLockTreatsStopped(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tif err := os.WriteFile(statusPath, []byte(\"{\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write malformed status: %v\", err)\n\t}\n\tout, err := (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"stop\")\n\tif err != nil {\n\t\tt.Fatalf(\"stop err = %v, want nil without held lock\", err)\n\t}\n\tif !strings.Contains(string(out), \"malformed status\") {\n\t\tt.Fatalf(\"stop output = %q, want malformed status note\", out)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStartLockHeldWithoutReadyStatusFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsystemdUnavailable := false\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t\tsystemdUserAvailable: &systemdUnavailable,\n\t\tlocalReadyTimeout: 20 * time.Millisecond,\n\t})\n\tif _, err := (teamsServiceLocalSupervisorBackend{}).Install(context.Background(), teamsServiceSpec{Executable: exePath, WorkingDir: tmp}); err != nil {\n\t\tt.Fatalf(\"install local supervisor: %v\", err)\n\t}\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlock := flock.New(lockPath)\n\tlocked, err := lock.TryLock()\n\tif err != nil || !locked {\n\t\tt.Fatalf(\"acquire test lock locked=%v err=%v\", locked, err)\n\t}\n\tdefer lock.Unlock()\n\n\t_, err = (teamsServiceLocalSupervisorBackend{}).Run(context.Background(), \"start\")\n\tif err == nil || !strings.Contains(err.Error(), \"lock is held but no verified supervisor status\") {\n\t\tt.Fatalf(\"start err = %v, want lock-held readiness failure\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthRestartsChild(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 200 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"health check restarted child\") {\n\t\tt.Fatalf(\"run child err = %v, want health restart\", err)\n\t}\n\tif status.ChildPID != 0 || status.ChildPGID != 0 || status.State != \"waiting\" {\n\t\tt.Fatalf(\"status after health restart = %#v, want cleared waiting child\", status)\n\t}\n\tif status.LastHealthReason != \"test stale child\" || status.LastHealthAction != teamsServiceWatchdogActionRestart {\n\t\tt.Fatalf(\"health status = reason %q action %q\", status.LastHealthReason, status.LastHealthAction)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildHealthTerminateErrorReturns(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local supervisor child process-group termination is Unix-only\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tfake := filepath.Join(tmp, \"fake-cxp\")\n\tif err := os.WriteFile(fake, []byte(\"#!/bin/sh\\nwhile :; do sleep 1; done\\n\"), 0o700); err != nil {\n\t\tt.Fatalf(\"write fake executable: %v\", err)\n\t}\n\tprevHeartbeat := teamsServiceLocalSupervisorHeartbeatEvery\n\tprevTerminate := teamsServiceLocalSupervisorTerminationWait\n\tprevHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tterminateErr := errors.New(\"test terminate failed\")\n\tvar captured *exec.Cmd\n\tteamsServiceLocalSupervisorHeartbeatEvery = 10 * time.Millisecond\n\tteamsServiceLocalSupervisorTerminationWait = 50 * time.Millisecond\n\tteamsServiceLocalSupervisorCheckChildHealth = func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionRestart, Reason: \"test stale child\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, _ time.Duration) error {\n\t\tcaptured = cmd\n\t\treturn terminateErr\n\t}\n\tt.Cleanup(func() {\n\t\tif captured != nil {\n\t\t\t_ = terminateTargetCommand(captured, 200*time.Millisecond)\n\t\t}\n\t\tteamsServiceLocalSupervisorHeartbeatEvery = prevHeartbeat\n\t\tteamsServiceLocalSupervisorTerminationWait = prevTerminate\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevTerminateTarget\n\t})\n\n\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\n\tdefer cancel()\n\tstatus := teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: filepath.Join(tmp, \"local-supervisor.json\"),\n\t\tSupervisorPID: os.Getpid(),\n\t\tSupervisorPGID: teamsLocalSupervisorCurrentProcessGroupID(),\n\t\tUpdatedAt: time.Now(),\n\t}\n\terr := runTeamsServiceLocalSupervisorChild(ctx, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: fake,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}, &status, io.Discard)\n\tif err == nil || !strings.Contains(err.Error(), \"could not terminate child\") {\n\t\tt.Fatalf(\"run child err = %v, want terminate failure\", err)\n\t}\n\tif !strings.Contains(status.LastError, terminateErr.Error()) {\n\t\tt.Fatalf(\"status LastError = %q, want terminate error\", status.LastError)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorProcessEnvUsesControlledEnvironment(t *testing.T) {\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(t.TempDir(), \"parent-home\"))\n\tt.Setenv(\"CODEX_PROXY_DEBUG\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:9999\")\n\tt.Setenv(\"XDG_RUNTIME_DIR\", filepath.Join(t.TempDir(), \"runtime\"))\n\tt.Setenv(\"XDG_CONFIG_HOME\", filepath.Join(t.TempDir(), \"config\"))\n\tspecHome := filepath.Join(t.TempDir(), \"spec-home\")\n\tenv := teamsServiceLocalSupervisorProcessEnv(map[string]string{\n\t\t\"CODEX_HOME\": specHome,\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\"HTTPS_PROXY\": \"http://configured-proxy.example.test:8080\",\n\t})\n\tif got, ok := testEnvValue(env, \"CODEX_HOME\"); !ok || got != specHome {\n\t\tt.Fatalf(\"CODEX_HOME env = %q ok=%v, want %q\", got, ok, specHome)\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n\tif got, ok := testEnvValue(env, \"HTTPS_PROXY\"); !ok || got != \"http://configured-proxy.example.test:8080\" {\n\t\tt.Fatalf(\"HTTPS_PROXY env = %q ok=%v, want configured service proxy\", got, ok)\n\t}\n\tif _, ok := testEnvValue(env, \"CODEX_PROXY_DEBUG\"); ok {\n\t\tt.Fatalf(\"unexpected non-service CODEX_PROXY_DEBUG in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"HTTP_PROXY\"); ok {\n\t\tt.Fatalf(\"unexpected parent HTTP_PROXY in local supervisor env: %#v\", env)\n\t}\n\tif _, ok := testEnvValue(env, \"XDG_RUNTIME_DIR\"); ok {\n\t\tt.Fatalf(\"unexpected XDG_RUNTIME_DIR in local supervisor env: %#v\", env)\n\t}\n\tif got, ok := testEnvValue(env, \"XDG_CONFIG_HOME\"); !ok || got == \"\" {\n\t\tt.Fatalf(\"XDG_CONFIG_HOME env = %q ok=%v, want preserved\", got, ok)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorChildProcessEnvMarksSupervisorVersion(t *testing.T) {\n\tenv := teamsServiceLocalSupervisorChildProcessEnv(map[string]string{\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t})\n\tif got, ok := testEnvValue(env, envTeamsLocalSupervisorVersion); !ok || got != buildVersion() {\n\t\tt.Fatalf(\"%s env = %q ok=%v, want %q\", envTeamsLocalSupervisorVersion, got, ok, buildVersion())\n\t}\n\tif got, ok := testEnvValue(env, \"CODEX_HELPER_TEAMS_SERVICE\"); !ok || got != \"1\" {\n\t\tt.Fatalf(\"CODEX_HELPER_TEAMS_SERVICE env = %q ok=%v, want 1\", got, ok)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSchedulesForOldSupervisor(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7001\n\tchildPID := 7002\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tvar gotName string\n\tvar gotArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tgotName = name\n\t\tgotArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"scheduled = false, want true\")\n\t}\n\tjoined := strings.Join(gotArgs, \" \")\n\tif gotName != \"sh\" ||\n\t\t!strings.Contains(joined, shellQuote(exe)+\" teams service restart\") ||\n\t\t!strings.Contains(joined, envTeamsLinuxServiceBackend+\"=local-supervisor\") {\n\t\tt.Fatalf(\"detached restart command = %q %#v, want local-supervisor restart\", gotName, gotArgs)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"scheduled\" || activation.TargetVersion != buildVersion() || activation.ObservedSupervisorEnv != \"\" || activation.OldSupervisorPID != supervisorPID || activation.OldChildPID != childPID {\n\t\tt.Fatalf(\"activation = %#v, want scheduled current upgrade handoff\", activation)\n\t}\n\tif activation.DeadlineAt.Sub(activation.ScheduledAt) != time.Minute {\n\t\tt.Fatalf(\"activation deadline delta = %s, want 1m\", activation.DeadlineAt.Sub(activation.ScheduledAt))\n\t}\n\n\tscheduled, err = maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"second maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"second scheduled = true, want false after one-shot guard\")\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartFailureQueuesControlNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\texe := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tsupervisorPID := 7101\n\tchildPID := 7102\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exe,\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, \"\")\n\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\n\tconfigPath, err := teamsServiceLocalSupervisorConfigPath()\n\tif err != nil {\n\t\tt.Fatalf(\"local supervisor config path: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorConfig(configPath, teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: exe,\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorStatus(teamsServiceLocalSupervisorStatus{\n\t\tVersion: teamsServiceLocalSupervisorStatusVersion,\n\t\tConfigPath: configPath,\n\t\tSupervisorPID: supervisorPID,\n\t\tChildPID: childPID,\n\t\tState: \"running\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor status: %v\", err)\n\t}\n\n\tprevAlive := teamsLocalSupervisorProcessAlive\n\tprevVerify := teamsLocalSupervisorVerifyProcessIdentity\n\tprevDetached := teamsServiceStartDetached\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Cleanup(func() {\n\t\tteamsLocalSupervisorProcessAlive = prevAlive\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevVerify\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Minute\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsCurrentProcessID = func() int { return childPID }\n\tteamsParentProcessID = func() int { return supervisorPID }\n\tteamsLocalSupervisorProcessAlive = func(pid int) bool {\n\t\treturn pid == supervisorPID || pid == childPID\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, gotConfigPath string) error {\n\t\tif pid != supervisorPID || gotConfigPath != configPath {\n\t\t\treturn fmt.Errorf(\"unexpected supervisor identity probe pid=%d config=%q\", pid, gotConfigPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\treturn errors.New(\"detached restart failed\")\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err == nil || !strings.Contains(err.Error(), \"detached restart failed\") {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error = %v, want detached failure\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false on detached failure\")\n\t}\n\tfor i := 0; i < 2; i++ {\n\t\tif err := queueLegacyLocalSupervisorActivationAttentionNoticeFromDisk(context.Background(), \"\"); err != nil {\n\t\t\tt.Fatalf(\"queue activation notice attempt %d: %v\", i, err)\n\t\t}\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tcount := 0\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-failed\" {\n\t\t\tcontinue\n\t\t}\n\t\tcount++\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"detached restart failed\") {\n\t\t\tt.Fatalf(\"queued failed activation notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif count != 1 {\n\t\tt.Fatalf(\"failed activation notices = %d, want exactly one\", count)\n\t}\n}\n\nfunc TestMaybeScheduleLegacyLocalSupervisorRestartSkipsCurrentSupervisorVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"1\")\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tt.Setenv(envTeamsLocalSupervisorVersion, buildVersion())\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: time.Now().Add(-time.Minute),\n\t\tDeadlineAt: time.Now().Add(time.Minute),\n\t\tUpdatedAt: time.Now().Add(-time.Minute),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsServiceStartDetached = prevDetached\n\t\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\t})\n\tteamsLegacyLocalSupervisorRestartScheduled.Store(false)\n\tteamsServiceStartDetached = func(context.Context, string, ...string) error {\n\t\tt.Fatal(\"matching local-supervisor version marker must not schedule a restart\")\n\t\treturn nil\n\t}\n\n\tscheduled, err := maybeScheduleLegacyLocalSupervisorRestart(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"maybeScheduleLegacyLocalSupervisorRestart error: %v\", err)\n\t}\n\tif scheduled {\n\t\tt.Fatal(\"scheduled = true, want false\")\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"success\" || activation.ObservedSupervisorEnv != buildVersion() {\n\t\tt.Fatalf(\"activation = %#v, want success with current marker\", activation)\n\t}\n}\n\nfunc TestWaitForLegacyLocalSupervisorActivationHandoffMarksExpired(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tprevWait := teamsLegacyLocalSupervisorActivationWait\n\tt.Cleanup(func() {\n\t\tteamsLegacyLocalSupervisorActivationWait = prevWait\n\t})\n\tteamsLegacyLocalSupervisorActivationWait = time.Nanosecond\n\tnow := time.Now().Add(-time.Minute)\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"scheduled\",\n\t\tTargetVersion: buildVersion(),\n\t\tScheduledAt: now,\n\t\tDeadlineAt: now.Add(time.Nanosecond),\n\t\tUpdatedAt: now,\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\n\tif err := waitForLegacyLocalSupervisorActivationHandoff(context.Background()); err != nil {\n\t\tt.Fatalf(\"waitForLegacyLocalSupervisorActivationHandoff error: %v\", err)\n\t}\n\tactivation, ok, err := readTeamsServiceLocalSupervisorActivation()\n\tif err != nil || !ok {\n\t\tt.Fatalf(\"read activation = ok %v err %v, want activation\", ok, err)\n\t}\n\tif activation.Status != \"expired\" || !strings.Contains(activation.Message, \"continuing under the existing supervisor\") {\n\t\tt.Fatalf(\"activation = %#v, want expired warning\", activation)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorStatusReportsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t})\n\tt.Setenv(envTeamsLinuxServiceBackend, \"local-supervisor\")\n\tif err := writeTeamsServiceLocalSupervisorConfig(filepath.Join(tmp, \"config\", \"codex-helper\", \"teams\", teamsServiceLocalSupervisorConfigName), teamsServiceLocalSupervisorConfig{\n\t\tVersion: teamsServiceLocalSupervisorConfigVersion,\n\t\tEnabled: true,\n\t\tSpec: teamsServiceSpec{\n\t\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\t\tWorkingDir: tmp,\n\t\t},\n\t}); err != nil {\n\t\tt.Fatalf(\"write local supervisor config: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorActivation(teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t\tUpdatedAt: time.Now(),\n\t}); err != nil {\n\t\tt.Fatalf(\"write activation: %v\", err)\n\t}\n\tbackend, err := teamsServiceBackendForCurrentPlatform()\n\tif err != nil {\n\t\tt.Fatalf(\"backend: %v\", err)\n\t}\n\tdata, err := backend.Run(context.Background(), \"status\")\n\tif err != nil {\n\t\tt.Fatalf(\"status: %v\", err)\n\t}\n\tgot := string(data)\n\tfor _, want := range []string{\"Activation: expired\", \"target=\" + buildVersion(), \"old_supervisor_pid=1234\", \"handoff timed out\"} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"status output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestQueueLegacyLocalSupervisorActivationAttentionNotice(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst, err := openTeamsStore()\n\tif err != nil {\n\t\tt.Fatalf(\"open store: %v\", err)\n\t}\n\tif err := st.Update(context.Background(), func(state *teamsstore.State) error {\n\t\tstate.ControlChat.TeamsChatID = \"control-chat\"\n\t\treturn nil\n\t}); err != nil {\n\t\tt.Fatalf(\"seed control chat: %v\", err)\n\t}\n\tactivation := teamsServiceLocalSupervisorActivation{\n\t\tVersion: teamsServiceLocalSupervisorActivationVersion,\n\t\tStatus: \"expired\",\n\t\tTargetVersion: buildVersion(),\n\t\tOldSupervisorPID: 1234,\n\t\tMessage: \"handoff timed out\",\n\t}\n\tif err := queueLegacyLocalSupervisorActivationAttentionNotice(context.Background(), \"\", activation); err != nil {\n\t\tt.Fatalf(\"queue notice: %v\", err)\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"load state: %v\", err)\n\t}\n\tfound := false\n\tfor _, msg := range state.OutboxMessages {\n\t\tif msg.Kind != \"local-supervisor-activation-expired\" {\n\t\t\tcontinue\n\t\t}\n\t\tfound = true\n\t\tif msg.TeamsChatID != \"control-chat\" || !msg.MentionOwner || !msg.UpgradeNonBlocking || !strings.Contains(msg.Body, \"handoff timed out\") {\n\t\t\tt.Fatalf(\"queued notice = %#v, want control attention notice\", msg)\n\t\t}\n\t}\n\tif !found {\n\t\tt.Fatalf(\"local-supervisor activation notice was not queued: %#v\", state.OutboxMessages)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRecordedIdentityChecksArgsStartTimeAndEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tlocalProcessArgs: func(pid int) ([]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"args pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn []string{filepath.Join(tmp, \"bin\", \"codex-proxy\"), \"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")}, nil\n\t\t},\n\t\tlocalProcessStartTime: func(pid int) (string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"start pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn \"12345\", nil\n\t\t},\n\t\tlocalProcessEnvironment: func(pid int) (map[string]string, error) {\n\t\t\tif pid != 7001 {\n\t\t\t\tt.Fatalf(\"env pid = %d, want 7001\", pid)\n\t\t\t}\n\t\t\treturn map[string]string{\n\t\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t\t}, nil\n\t\t},\n\t})\n\terr := teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tExecutable: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tArgs: []string{\"teams\", \"run\", \"--registry\", filepath.Join(tmp, \"registry.json\")},\n\t\tProcStartTime: \"12345\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"work\",\n\t\t\t\"CODEX_HELPER_TEAMS_MACHINE_ID\": \"machine-a\",\n\t\t\t\"CODEX_HELPER_TEAMS_AUTH_PROFILE\": \"auth\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err != nil {\n\t\tt.Fatalf(\"verify recorded identity: %v\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tProcStartTime: \"different\",\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"start time changed\") {\n\t\tt.Fatalf(\"verify stale start time err = %v, want start time rejection\", err)\n\t}\n\terr = teamsServiceLocalSupervisorVerifyRecordedIdentity(7001, &teamsServiceLocalSupervisorProcessIdentity{\n\t\tArgs: []string{\"teams\", \"run\"},\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_PROFILE\": \"other\",\n\t\t},\n\t}, \"local supervisor child\")\n\tif err == nil || !strings.Contains(err.Error(), \"environment CODEX_HELPER_TEAMS_PROFILE\") {\n\t\tt.Fatalf(\"verify env mismatch err = %v, want env rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogRotationFailureKeepsCurrentFile(t *testing.T) {\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif err := os.WriteFile(logPath, large, 0o600); err != nil {\n\t\tt.Fatalf(\"write large log: %v\", err)\n\t}\n\tcurrent, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY, 0o600)\n\tif err != nil {\n\t\tt.Fatalf(\"open current log: %v\", err)\n\t}\n\tdefer current.Close()\n\tif err := os.WriteFile(logPath+\".2\", []byte(\"backup\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write backup log: %v\", err)\n\t}\n\tif err := os.Mkdir(logPath+\".3\", 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir conflicting backup path: %v\", err)\n\t}\n\tif err := os.WriteFile(filepath.Join(logPath+\".3\", \"block\"), []byte(\"block\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write conflicting backup child: %v\", err)\n\t}\n\n\tnext, err := reopenTeamsServiceLocalSupervisorLogIfNeeded(logPath, current)\n\tif err == nil {\n\t\tt.Fatal(\"reopen log succeeded, want rotation error\")\n\t}\n\tif next != current {\n\t\tt.Fatalf(\"reopen returned different file after failure\")\n\t}\n\tif _, err := current.Write([]byte(\"still-open\\n\")); err != nil {\n\t\tt.Fatalf(\"current log file was closed after rotation failure: %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorLogWriterRotatesDuringLongRunningOutput(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"local-supervisor log rotation renames an open append handle; Windows uses the Task Scheduler backend\")\n\t}\n\ttmp := t.TempDir()\n\tlogPath := filepath.Join(tmp, \"local-supervisor.log\")\n\twriter, err := openTeamsServiceLocalSupervisorLogWriter(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"open log writer: %v\", err)\n\t}\n\tlarge := bytes.Repeat([]byte(\"x\"), teamsServiceLocalSupervisorMaxLogBytes+1)\n\tif _, err := writer.Write(large); err != nil {\n\t\tt.Fatalf(\"write large log chunk: %v\", err)\n\t}\n\tif _, err := writer.Write([]byte(\"next\\n\")); err != nil {\n\t\tt.Fatalf(\"write post-rotation log chunk: %v\", err)\n\t}\n\tif err := writer.Close(); err != nil {\n\t\tt.Fatalf(\"close log writer: %v\", err)\n\t}\n\tcurrent, err := os.ReadFile(logPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read current log: %v\", err)\n\t}\n\tif string(current) != \"next\\n\" {\n\t\tt.Fatalf(\"current log = %q, want post-rotation chunk\", current)\n\t}\n\trotated, err := os.Stat(logPath + \".1\")\n\tif err != nil {\n\t\tt.Fatalf(\"stat rotated log: %v\", err)\n\t}\n\tif rotated.Size() <= teamsServiceLocalSupervisorMaxLogBytes {\n\t\tt.Fatalf(\"rotated log size = %d, want > %d\", rotated.Size(), teamsServiceLocalSupervisorMaxLogBytes)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorDirRejectsSymlink(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\ttmp := t.TempDir()\n\ttarget := filepath.Join(tmp, \"target\")\n\tif err := os.Mkdir(target, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir target: %v\", err)\n\t}\n\tlink := filepath.Join(tmp, \"link\")\n\tif err := os.Symlink(target, link); err != nil {\n\t\tt.Fatalf(\"symlink: %v\", err)\n\t}\n\terr := ensureTeamsServiceLocalSupervisorDir(link)\n\tif err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"ensure symlink dir err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorRejectsSymlinkStatusLockAndLogFiles(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"symlink creation is not reliably available on Windows test hosts\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tstatusPath, err := teamsServiceLocalSupervisorStatusPath()\n\tif err != nil {\n\t\tt.Fatalf(\"status path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(statusPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir status dir: %v\", err)\n\t}\n\tstatusTarget := filepath.Join(tmp, \"status-target.json\")\n\tif err := os.WriteFile(statusTarget, []byte(\"{}\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write status target: %v\", err)\n\t}\n\tif err := os.Symlink(statusTarget, statusPath); err != nil {\n\t\tt.Fatalf(\"status symlink: %v\", err)\n\t}\n\tif _, _, err := readTeamsServiceLocalSupervisorStatus(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"read symlink status err = %v, want symlink rejection\", err)\n\t}\n\n\tlockPath, err := teamsServiceLocalSupervisorLockPath()\n\tif err != nil {\n\t\tt.Fatalf(\"lock path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(lockPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir lock dir: %v\", err)\n\t}\n\tlockTarget := filepath.Join(tmp, \"lock-target\")\n\tif err := os.WriteFile(lockTarget, []byte(\"lock\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write lock target: %v\", err)\n\t}\n\tif err := os.Symlink(lockTarget, lockPath); err != nil {\n\t\tt.Fatalf(\"lock symlink: %v\", err)\n\t}\n\tif _, err := teamsServiceLocalSupervisorLockHeld(); err == nil || !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"lock symlink err = %v, want symlink rejection\", err)\n\t}\n\n\tlogPath, err := teamsServiceLocalSupervisorLogPath()\n\tif err != nil {\n\t\tt.Fatalf(\"log path: %v\", err)\n\t}\n\tif err := os.MkdirAll(filepath.Dir(logPath), 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir log dir: %v\", err)\n\t}\n\tlogTarget := filepath.Join(tmp, \"log-target\")\n\tif err := os.WriteFile(logTarget, []byte(\"log\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write log target: %v\", err)\n\t}\n\tif err := os.Symlink(logTarget, logPath); err != nil {\n\t\tt.Fatalf(\"log symlink: %v\", err)\n\t}\n\tif file, err := openTeamsServiceLocalSupervisorLog(logPath); err == nil {\n\t\t_ = file.Close()\n\t\tt.Fatal(\"open symlink log succeeded, want rejection\")\n\t} else if !strings.Contains(err.Error(), \"must not be a symlink\") {\n\t\tt.Fatalf(\"log symlink err = %v, want symlink rejection\", err)\n\t}\n}\n\nfunc TestTeamsServiceLocalSupervisorFileAtomicReplacesExistingFile(t *testing.T) {\n\tpath := filepath.Join(t.TempDir(), \"status.json\")\n\tif err := os.WriteFile(path, []byte(\"old\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write old file: %v\", err)\n\t}\n\tif err := writeTeamsServiceLocalSupervisorFileAtomic(path, []byte(\"new\"), 0o600); err != nil {\n\t\tt.Fatalf(\"atomic write: %v\", err)\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\tt.Fatalf(\"read replaced file: %v\", err)\n\t}\n\tif string(data) != \"new\" {\n\t\tt.Fatalf(\"file content = %q, want new\", data)\n\t}\n}\n\nfunc TestTeamsServiceInstallUsesStablePathWhenExecutableIsNFSSillyRename(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"NFS silly rename handling is for Unix service units\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\tbinDir := filepath.Join(tmp, \"bin\")\n\tif err := os.MkdirAll(binDir, 0o755); err != nil {\n\t\tt.Fatalf(\"create bin dir: %v\", err)\n\t}\n\tstable := filepath.Join(binDir, \"codex-proxy\")\n\tif err := os.WriteFile(stable, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write stable binary: %v\", err)\n\t}\n\trunning := filepath.Join(binDir, \".nfs802014de01c482a800000492\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: running,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"ExecStart=\" + systemdQuoteArg(stable) + \" teams run --owner-stale-after 1m30s --auto-service=false\",\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+stable),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tif strings.Contains(unit, running) {\n\t\tt.Fatalf(\"unit should not retain NFS silly-renamed path %q:\\n%s\", running, unit)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapDryRunDoesNotWriteOrStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd dry-run rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--dry-run\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --dry-run error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"dry-run should not call service runner, calls=%#v\", runner.calls)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); !os.IsNotExist(err) {\n\t\tt.Fatalf(\"dry-run should not write unit, stat err=%v\", err)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"Teams service dry-run\") || !strings.Contains(got, \"--- systemd main unit ---\") || !strings.Contains(got, exePath) {\n\t\tt.Fatalf(\"dry-run output missing rendered unit details:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartRepairsButDoesNotStart(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd no-start rendering is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tif err := os.MkdirAll(filepath.Dir(exePath), 0o755); err != nil {\n\t\tt.Fatalf(\"mkdir bin: %v\", err)\n\t}\n\tif err := os.WriteFile(exePath, []byte(\"stable\"), 0o755); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\", err)\n\t}\n\tif _, err := os.Stat(filepath.Join(unitDir, teamsServiceUnitName)); err != nil {\n\t\tt.Fatalf(\"no-start should write unit: %v\", err)\n\t}\n\tfor _, forbidden := range []string{\"start\", \"restart\"} {\n\t\tif teamsServiceCallSeen(runner.calls, forbidden) {\n\t\t\tt.Fatalf(\"no-start should not %s service, calls=%#v\", forbidden, runner.calls)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"enable\") {\n\t\tt.Fatalf(\"no-start should enable repaired service, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(out.String(), \"Service was repaired but not started.\") {\n\t\tt.Fatalf(\"no-start output missing safety message:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceInstallWithoutRegistryLetsBridgeUseScopedDefaults(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"--registry\") {\n\t\tt.Fatalf(\"default service unit should not force a shared legacy registry:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"ExecStart=\"+systemdQuoteArg(exePath)+\" teams run\") {\n\t\tt.Fatalf(\"unit missing teams run ExecStart:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceInstallRejectsGoRunTemporaryExecutable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"temporary helper executable path\") {\n\t\tt.Fatalf(\"expected go run executable rejection, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceAuthPreflightRequiresForegroundLogin(t *testing.T) {\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\n\terr := defaultTeamsServiceAuthPreflight()\n\tif err == nil || !strings.Contains(err.Error(), \"teams auth\") {\n\t\tt.Fatalf(\"expected missing auth preflight error, got %v\", err)\n\t}\n}\n\nfunc TestTeamsServiceStartRequiresAuthPreflight(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"started\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"start\"})\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"auth missing\") {\n\t\tt.Fatalf(\"expected auth preflight error, got %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"service start should not reach supervisor when auth preflight fails: %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversActiveOwnerBeforeRestart(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service restart --force: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Cleared stale owners: 1\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Restarted Teams service\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif !teamsServiceCallSeen(runner.calls, \"restart\") {\n\t\tt.Fatalf(\"restart --force did not restart service, calls=%#v\", runner.calls)\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared after forced service restart\")\n\t}\n\tstate, err := st.Load(context.Background())\n\tif err != nil {\n\t\tt.Fatalf(\"Load error: %v\", err)\n\t}\n\tif got := state.Turns[\"turn:manual\"].Status; got != teamsstore.TurnStatusInterrupted {\n\t\tt.Fatalf(\"turn status = %q, want interrupted\", got)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsAuthAndExecutableWithoutFailingFast(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"go-build123\", \"b001\", \"exe\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\tteamsServiceAuthPreflight = func() error { return errors.New(\"auth missing\") }\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service doctor should report auth/executable issues without failing fast: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: systemd-user\",\n\t\t\"Teams service raw executable:\",\n\t\t\"Teams service stable executable: unresolved\",\n\t\t\"temporary helper executable path\",\n\t\t\"Teams service auth: not ready\",\n\t\t\"auth missing\",\n\t\t\"Linux: systemd --user keeps the Teams bridge independent of the terminal\",\n\t\t\"if lingering is disabled\",\n\t\t\"no user service can guarantee survival\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesScopedEnvironment(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"work-profile\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE\", filepath.Join(tmp, \"read-token.json\"))\n\tt.Setenv(envTeamsASRCommand, filepath.Join(tmp, \"bin\", \"teams-asr\"))\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--threads={threads}\"]`)\n\tt.Setenv(\"HTTPS_PROXY\", \"http://proxy.example.test:8080\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"codex home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_PROFILE=work-profile\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_ALLOW_UNSAFE_SCOPES=1\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_READ_TOKEN_CACHE=\"+filepath.Join(tmp, \"read-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRCommand+\"=\"+filepath.Join(tmp, \"bin\", \"teams-asr\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsASRArgsJSON+`=[\"--input={input}\",\"--threads={threads}\"]`),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_SERVICE_MODE=background\"),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing preserved env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsASRArgsFromEnvAndServiceOverrides(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(envTeamsASRArgsJSON, `[\"--input={input}\",\"--prefix= keep spaces \",\"--threads={threads}\"]`)\n\targs, err := teamsASRArgsFromFlagsOrEnv(nil, true)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsASRArgsFromFlagsOrEnv: %v\", err)\n\t}\n\tif strings.Join(args, \"\\n\") != \"--input={input}\\n--prefix= keep spaces \\n--threads={threads}\" {\n\t\tt.Fatalf(\"ASR args = %#v\", args)\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\tspec, err := buildTeamsServiceSpec(\n\t\tstringPtr(\"registry.json\"),\n\t\tteamsServiceSpecEnvironmentOverrides(teamsASRServiceEnvironmentOverrides(filepath.Join(tmp, \"asr\"), []string{\"--input={input}\", \"--threads={threads}\"})),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"buildTeamsServiceSpec: %v\", err)\n\t}\n\tif spec.Environment[envTeamsASRCommand] != filepath.Join(tmp, \"asr\") {\n\t\tt.Fatalf(\"ASR command env = %q\", spec.Environment[envTeamsASRCommand])\n\t}\n\tif spec.Environment[envTeamsASRArgsJSON] != `[\"--input={input}\",\"--threads={threads}\"]` {\n\t\tt.Fatalf(\"ASR args env = %q\", spec.Environment[envTeamsASRArgsJSON])\n\t}\n}\n\nfunc TestTeamsServiceInstallPreservesBeaconAndUpdateEnvironmentAndBlocksVolatile(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_BEACON_STORE\", filepath.Join(tmp, \"beacon.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTH_CONFIG\", filepath.Join(tmp, \"auth.json\"))\n\tt.Setenv(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE\", filepath.Join(tmp, \"full-token.json\"))\n\tt.Setenv(beacon.BeaconSlurmQueryCommandEnv, filepath.Join(tmp, \"slurm-query\"))\n\tt.Setenv(beacon.BeaconSlurmSubmitCommandEnv, filepath.Join(tmp, \"slurm-submit\"))\n\tt.Setenv(beacon.BeaconLSFCancelCommandEnv, filepath.Join(tmp, \"lsf-cancel\"))\n\tt.Setenv(update.EnvRepo, \"owner/name\")\n\tt.Setenv(update.EnvUpdateIndexURL, \"https://example.test/releases.json\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://user:pass@proxy.example.test:8080\")\n\tt.Setenv(envTeamsHelperCLIPath, \"/tmp/.nfs802014de01c482a800000492\")\n\tt.Setenv(envTeamsHelperCLIDir, \"/tmp\")\n\tt.Setenv(envCodexProxyWrapperExe, \"/tmp/.nfswrapper\")\n\tt.Setenv(update.EnvInstallDir, \"/tmp/.nfsbad\")\n\tt.Setenv(update.EnvVersion, \"v0.0.0-bad\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_CLIENT_SECRET\", \"secret\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_BEARER_TOKEN\", \"token\")\n\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_AUTH_CONFIG=\"+filepath.Join(tmp, \"auth.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_TEAMS_FULL_TOKEN_CACHE=\"+filepath.Join(tmp, \"full-token.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmSubmitCommandEnv+\"=\"+filepath.Join(tmp, \"slurm-submit\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconLSFCancelCommandEnv+\"=\"+filepath.Join(tmp, \"lsf-cancel\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvRepo+\"=owner/name\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://example.test/releases.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvInstallDir+\"=\"+exePath),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"unit missing env %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{\n\t\tenvTeamsHelperCLIPath + \"=\",\n\t\tenvTeamsHelperCLIDir + \"=\",\n\t\tenvCodexProxyWrapperExe + \"=\",\n\t\tupdate.EnvInstallDir + \"=/tmp/.nfsbad\",\n\t\tupdate.EnvVersion + \"=\",\n\t\t\"CODEX_HELPER_TEAMS_CLIENT_SECRET=\",\n\t\t\"CODEX_HELPER_TEAMS_BEARER_TOKEN=\",\n\t\t\"HTTPS_PROXY=\",\n\t\t\"user:pass@proxy.example.test\",\n\t} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"unit contains forbidden env %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairMergesAllowedEnvironmentFromExistingSystemdUnit(t *testing.T) {\n\tif runtime.GOOS == \"windows\" {\n\t\tt.Skip(\"systemd unit merge test is Unix-focused\")\n\t}\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\toldUnit := strings.Join([]string{\n\t\t\"[Service]\",\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(envTeamsHelperCLIPath+\"=/tmp/.nfsbad\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTPS_PROXY=http://user:pass@proxy.example.test:8080\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"HTTP_PROXY=user:pass@proxy.example.test:8080\"),\n\t}, \"\\n\")\n\tif err := os.WriteFile(filepath.Join(unitDir, teamsServiceUnitName), []byte(oldUnit), 0o600); err != nil {\n\t\tt.Fatalf(\"write old unit: %v\", err)\n\t}\n\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit: %v\", err)\n\t}\n\tunit := string(data)\n\tfor _, want := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HELPER_BEACON_STORE=\"+filepath.Join(tmp, \"old-beacon.json\")),\n\t\t\"Environment=\" + systemdQuoteArg(beacon.BeaconSlurmQueryCommandEnv+\"=\"+filepath.Join(tmp, \"old-slurm-query\")),\n\t\t\"Environment=\" + systemdQuoteArg(update.EnvUpdateIndexURL+\"=https://old.example.test/index.json\"),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+filepath.Join(tmp, \"old-codex-home\")),\n\t} {\n\t\tif !strings.Contains(unit, want) {\n\t\t\tt.Fatalf(\"merged unit missing %q:\\n%s\", want, unit)\n\t\t}\n\t}\n\tfor _, forbidden := range []string{envTeamsHelperCLIPath + \"=/tmp/.nfsbad\", \"user:pass@proxy.example.test\"} {\n\t\tif strings.Contains(unit, forbidden) {\n\t\t\tt.Fatalf(\"merged unit contains forbidden %q:\\n%s\", forbidden, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDefaultsCodexHomeForScope(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\twant := filepath.Join(home, \".codex\")\n\tunit := string(data)\n\tfor _, envLine := range []string{\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_HOME=\"+want),\n\t\t\"Environment=\" + systemdQuoteArg(\"CODEX_DIR=\"+want),\n\t} {\n\t\tif !strings.Contains(unit, envLine) {\n\t\t\tt.Fatalf(\"unit missing default Codex home env %q:\\n%s\", envLine, unit)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallDoesNotFailWhenDefaultCodexHomeCannotBeDerived(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return \"\", errors.New(\"home unavailable\") }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: exePath,\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: unitDir,\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"service install should not fail only because default Codex home cannot be derived: %v\", err)\n\t}\n\tdata, err := os.ReadFile(filepath.Join(unitDir, teamsServiceUnitName))\n\tif err != nil {\n\t\tt.Fatalf(\"read unit file: %v\", err)\n\t}\n\tunit := string(data)\n\tif strings.Contains(unit, \"CODEX_HOME=\") || strings.Contains(unit, \"CODEX_DIR=\") {\n\t\tt.Fatalf(\"unit should omit default Codex home env when it cannot be derived:\\n%s\", unit)\n\t}\n\tif !strings.Contains(unit, \"CODEX_HELPER_TEAMS_SERVICE=1\") {\n\t\tt.Fatalf(\"unit missing required helper service env:\\n%s\", unit)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesLoopbackProxyByDefault(t *testing.T) {\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value == \"\" {\n\t\t\tt.Fatalf(\"%s should be preserved for the background service, got %#v\", name, env)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentMirrorsSingleCodexHomeEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex home\"))\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(tmp, \"codex home\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentResolvesRelativeCodexDirEnv(t *testing.T) {\n\ttmp := t.TempDir()\n\twork := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(work)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\twant := filepath.Join(work, \"relative-codex\")\n\tif env[\"CODEX_HOME\"] != want || env[\"CODEX_DIR\"] != want {\n\t\tt.Fatalf(\"Codex home env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"], want)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentPreservesConflictingExplicitCodexHomes(t *testing.T) {\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"codex-home\")\n\tdir := filepath.Join(tmp, \"codex-dir\")\n\tt.Setenv(\"CODEX_HOME\", home)\n\tt.Setenv(\"CODEX_DIR\", dir)\n\n\tenv, err := teamsServiceEnvironmentForWorkingDir(tmp)\n\tif err != nil {\n\t\tt.Fatalf(\"teamsServiceEnvironmentForWorkingDir: %v\", err)\n\t}\n\tif env[\"CODEX_HOME\"] != home || env[\"CODEX_DIR\"] != dir {\n\t\tt.Fatalf(\"explicit Codex env should be preserved, got CODEX_HOME:%q CODEX_DIR:%q\", env[\"CODEX_HOME\"], env[\"CODEX_DIR\"])\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanDropLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTP_PROXY\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://localhost:38471\")\n\tt.Setenv(\"ALL_PROXY\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"http_proxy\", \"http://127.0.0.1:38471\")\n\tt.Setenv(\"https_proxy\", \"http://localhost:38471\")\n\tt.Setenv(\"all_proxy\", \"socks5://[::1]:38471\")\n\tt.Setenv(\"NO_PROXY\", \"localhost,127.0.0.1,::1\")\n\tt.Setenv(\"no_proxy\", \"localhost,127.0.0.1,::1\")\n\n\tenv := teamsServiceEnvironment()\n\tfor _, name := range []string{\"HTTP_PROXY\", \"HTTPS_PROXY\", \"ALL_PROXY\", \"http_proxy\", \"https_proxy\", \"all_proxy\"} {\n\t\tif value := env[name]; value != \"\" {\n\t\t\tt.Fatalf(\"%s = %q, want dropped loopback proxy\", name, value)\n\t\t}\n\t}\n\tif env[\"NO_PROXY\"] == \"\" || env[\"no_proxy\"] == \"\" {\n\t\tt.Fatalf(\"NO_PROXY values should be preserved, got %#v\", env)\n\t}\n}\n\nfunc TestTeamsServiceEnvironmentCanKeepLoopbackProxyWhenExplicit(t *testing.T) {\n\tt.Setenv(\"CODEX_HELPER_TEAMS_DROP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_KEEP_LOCAL_PROXY\", \"1\")\n\tt.Setenv(\"HTTPS_PROXY\", \"http://127.0.0.1:38471\")\n\n\tenv := teamsServiceEnvironment()\n\tif got := env[\"HTTPS_PROXY\"]; got != \"http://127.0.0.1:38471\" {\n\t\tt.Fatalf(\"HTTPS_PROXY = %q, want explicit loopback proxy preserved\", got)\n\t}\n}\n\nfunc TestTeamsServiceWSLArgumentsUseExecToBypassLoginShell(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tspec := teamsServiceSpec{\n\t\tExecutable: \"/home/baka/.local/bin/codex-proxy\",\n\t\tWorkingDir: \"/home/baka\",\n\t\tEnvironment: map[string]string{\n\t\t\t\"CODEX_HELPER_TEAMS_SERVICE\": \"1\",\n\t\t\t\"NO_PROXY\": \"*.nvidia.com,nvidia.com,\",\n\t\t},\n\t}\n\targs := buildTeamsServiceWSLArguments(spec)\n\tjoined := strings.Join(args, \"\\x00\")\n\tif strings.Contains(joined, \"\\x00--\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should not route env through the login shell: %#v\", args)\n\t}\n\tif !strings.Contains(joined, \"\\x00--exec\\x00env\\x00\") {\n\t\tt.Fatalf(\"WSL service args should use --exec env to preserve glob proxy values: %#v\", args)\n\t}\n\tif !slices.Contains(args, \"NO_PROXY=*.nvidia.com,nvidia.com,\") {\n\t\tt.Fatalf(\"WSL service args should preserve raw NO_PROXY value for env: %#v\", args)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskIdentityStableAcrossWorkingConfigAndCodexHome(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\tisWSL: true,\n\t\twslDistro: \"Debian\",\n\t\twslLinuxUser: \"baka\",\n\t})\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_MACHINE_ID\", \"\")\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/a/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/a/.codex\")\n\tfirst := teamsServiceWSLTaskIdentity()\n\n\tt.Setenv(\"CODEX_HELPER_CONFIG\", \"/home/baka/project/b/config.json\")\n\tt.Setenv(\"CODEX_HOME\", \"/home/baka/project/b/.codex\")\n\tsecond := teamsServiceWSLTaskIdentity()\n\n\tif first.Suffix != second.Suffix || first.Display != second.Display {\n\t\tt.Fatalf(\"WSL task identity changed across cwd/config/codex home: first=%#v second=%#v\", first, second)\n\t}\n\tif !strings.Contains(first.Display, \"Debian baka default\") {\n\t\tt.Fatalf(\"display = %q, want distro/user/profile\", first.Display)\n\t}\n}\n\nfunc TestTeamsServiceWSLTaskNamePrefixIncludesTrailingSpaceBeforeSuffix(t *testing.T) {\n\tgot := teamsServiceWSLTaskNamePrefix(\"Codex Helper Teams Bridge (WSL Debian baka default bd54a914bb9b)\")\n\twant := \"Codex Helper Teams Bridge (WSL Debian baka default \"\n\tif got != want {\n\t\tt.Fatalf(\"prefix = %q, want %q\", got, want)\n\t}\n}\n\nfunc TestTeamsServiceIsWSLFromSignalsRequiresInteropForKernelOnlyMatch(t *testing.T) {\n\tcases := []struct {\n\t\tname string\n\t\tgoos string\n\t\tprocVersion string\n\t\tinteropAvailable bool\n\t\twant bool\n\t}{\n\t\t{\n\t\t\tname: \"real WSL signal with interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: true,\n\t\t},\n\t\t{\n\t\t\tname: \"docker container on WSL kernel without interop\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: false,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"ordinary linux\",\n\t\t\tgoos: \"linux\",\n\t\t\tprocVersion: \"Linux version 6.8.0-azure\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t\t{\n\t\t\tname: \"windows host\",\n\t\t\tgoos: \"windows\",\n\t\t\tprocVersion: \"Linux version 6.6.87.2-microsoft-standard-WSL2\",\n\t\t\tinteropAvailable: true,\n\t\t\twant: false,\n\t\t},\n\t}\n\tfor _, tc := range cases {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tif got := teamsServiceIsWSLFromSignals(tc.goos, tc.procVersion, tc.interopAvailable); got != tc.want {\n\t\t\t\tt.Fatalf(\"isWSL = %t, want %t\", got, tc.want)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestDefaultTeamsServiceIsWSLRequiresLinuxBeforeEnv(t *testing.T) {\n\tprevGOOS := teamsServiceGOOS\n\tt.Cleanup(func() { teamsServiceGOOS = prevGOOS })\n\tt.Setenv(\"WSL_DISTRO_NAME\", \"Ubuntu\")\n\tt.Setenv(\"WSL_INTEROP\", \"/run/WSL/1_interop\")\n\n\tteamsServiceGOOS = func() string { return \"windows\" }\n\tif got := defaultTeamsServiceIsWSL(); got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on windows with WSL env = %t, want false\", got)\n\t}\n\n\tteamsServiceGOOS = func() string { return \"linux\" }\n\tif got := defaultTeamsServiceIsWSL(); !got {\n\t\tt.Fatalf(\"defaultTeamsServiceIsWSL on linux with WSL env = %t, want true\", got)\n\t}\n}\n\nfunc TestTeamsServiceSystemctlSubcommandsUseUserService(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{output: []byte(\"active\\n\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tfor _, tc := range []struct {\n\t\tname string\n\t\twantCalls []teamsServiceCommandCall\n\t}{\n\t\t{name: \"enable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"enable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"disable\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"disable\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"status\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"status\", \"--no-pager\", teamsServiceWatchdogTimerName}},\n\t\t}},\n\t\t{name: \"start\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"start\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t\t{name: \"stop\", wantCalls: []teamsServiceCommandCall{\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t\t}},\n\t\t{name: \"restart\", wantCalls: []teamsServiceCommandCall{{name: \"systemctl\", args: []string{\"--user\", \"restart\", teamsServiceUnitName, teamsServiceWatchdogUnitName, teamsServiceWatchdogTimerName}}}},\n\t} {\n\t\trunner.calls = nil\n\t\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\t\tcmd.SetArgs([]string{tc.name})\n\t\tvar out bytes.Buffer\n\t\tcmd.SetOut(&out)\n\t\tif err := cmd.Execute(); err != nil {\n\t\t\tt.Fatalf(\"execute service %s: %v\", tc.name, err)\n\t\t}\n\t\tif !reflect.DeepEqual(runner.calls, tc.wantCalls) {\n\t\t\tt.Fatalf(\"%s calls = %#v, want %#v\", tc.name, runner.calls, tc.wantCalls)\n\t\t}\n\t\tif tc.name == \"status\" && !strings.Contains(out.String(), \"active\") {\n\t\t\tt.Fatalf(\"status should print systemctl output:\\n%s\", out.String())\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopIgnoresMissingWatchdogUnits(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Unit codex-helper-teams-watchdog.timer not loaded.\\n\"),\n\t\t\tnil,\n\t\t\t[]byte(\"stopped main\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 5\"),\n\t\t\terrors.New(\"systemctl --user stop failed: unit could not be found\"),\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service stop: %v\\n%s\", err, out.String())\n\t}\n\twantCalls := []teamsServiceCommandCall{\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogTimerName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceWatchdogUnitName}},\n\t\t{name: \"systemctl\", args: []string{\"--user\", \"stop\", teamsServiceUnitName}},\n\t}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"stop calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n\tif strings.Contains(out.String(), \"not loaded\") || strings.Contains(out.String(), \"could not be found\") {\n\t\tt.Fatalf(\"missing watchdog errors should be suppressed:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(out.String(), \"Stopped Teams service\") {\n\t\tt.Fatalf(\"stop success output missing:\\n%s\", out.String())\n\t}\n}\n\nfunc TestTeamsServiceSystemctlStopStillFailsWhenPrimaryStopFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\terrs: []error{nil, nil, errors.New(\"exit status 5\")},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"stop\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"exit status 5\") {\n\t\tt.Fatalf(\"execute service stop error = %v, want primary stop failure\\n%s\", err, out.String())\n\t}\n}\n\nfunc TestTeamsServiceUninstallRemovesUnitAndReloads(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tunitDir := filepath.Join(tmp, \"systemd\", \"user\")\n\tif err := os.MkdirAll(unitDir, 0o700); err != nil {\n\t\tt.Fatalf(\"mkdir unit dir: %v\", err)\n\t}\n\tunitPath := filepath.Join(unitDir, teamsServiceUnitName)\n\tif err := os.WriteFile(unitPath, []byte(\"stale\"), 0o600); err != nil {\n\t\tt.Fatalf(\"write stale unit: %v\", err)\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: unitDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tif _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {\n\t\tt.Fatalf(\"expected unit to be removed, stat err=%v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"systemctl\",\n\t\targs: []string{\"--user\", \"daemon-reload\"},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"systemctl calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceUnsupportedPlatformInjection(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"freebsd\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"install\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatal(\"expected unsupported platform error\")\n\t}\n\tif !strings.Contains(err.Error(), `unsupported platform \"freebsd\"`) ||\n\t\t!strings.Contains(err.Error(), \"Linux systemd --user, macOS LaunchAgent, and Windows per-user Task Scheduler\") {\n\t\tt.Fatalf(\"unexpected unsupported error: %v\", err)\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"unsupported platform should not invoke systemctl, calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesMacOSLaunchAgentPlist(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tagentDir := filepath.Join(tmp, \"LaunchAgents\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"darwin\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\tlaunchAgentDir: agentDir,\n\t\tuserID: \"501\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\tplistPath := filepath.Join(agentDir, teamsServiceLaunchAgentPlistName)\n\tdata, err := os.ReadFile(plistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read plist: %v\", err)\n\t}\n\tplist := string(data)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentLabel + \"\",\n\t\t\"Disabled\",\n\t\t\"ProgramArguments\",\n\t\t\"\" + exePath + \"\",\n\t\t\"teams\",\n\t\t\"run\",\n\t\t\"--owner-stale-after\",\n\t\t\"1m30s\",\n\t\t\"--auto-service=false\",\n\t\t\"--registry\",\n\t\t\"\" + registryPath + \"\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t\t\"CODEX_HELPER_TEAMS_SERVICE\",\n\t} {\n\t\tif !strings.Contains(plist, want) {\n\t\t\tt.Fatalf(\"plist missing %q:\\n%s\", want, plist)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"install should only write plist, calls=%#v\", runner.calls)\n\t}\n\twatchdogPlistPath := filepath.Join(agentDir, teamsServiceLaunchAgentWatchdogPlistName)\n\twatchdogData, err := os.ReadFile(watchdogPlistPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog plist: %v\", err)\n\t}\n\twatchdogPlist := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"\" + teamsServiceLaunchAgentWatchdogLabel + \"\",\n\t\t\"teams\",\n\t\t\"service\",\n\t\t\"watchdog\",\n\t\t\"--loop\",\n\t\t\"--interval\",\n\t\t\"10s\",\n\t\t\"--quiet\",\n\t\t\"KeepAlive\",\n\t\t\"SuccessfulExit\",\n\t\t\"\",\n\t} {\n\t\tif !strings.Contains(watchdogPlist, want) {\n\t\t\tt.Fatalf(\"watchdog plist missing %q:\\n%s\", want, watchdogPlist)\n\t\t}\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service start: %v\", err)\n\t}\n\twantCalls := []teamsServiceCommandCall{{\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", plistPath},\n\t}, {\n\t\tname: \"launchctl\",\n\t\targs: []string{\"bootstrap\", \"gui/501\", watchdogPlistPath},\n\t}}\n\tif !reflect.DeepEqual(runner.calls, wantCalls) {\n\t\tt.Fatalf(\"launchctl start calls = %#v, want %#v\", runner.calls, wantCalls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWindowsTaskXMLAndRegistersTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\txmlDir := filepath.Join(tmp, \"config\")\n\texePath := filepath.Join(tmp, \"bin\", \"codex-proxy.exe\")\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exePath,\n\t\tcwd: cwd,\n\t\twindowsTaskDir: xmlDir,\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\n\txmlPath := filepath.Join(xmlDir, teamsServiceWindowsTaskXMLName)\n\tdata, err := os.ReadFile(xmlPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read task xml: %v\", err)\n\t}\n\ttaskXML := string(data)\n\tfor _, want := range []string{\n\t\t\"InteractiveToken\",\n\t\t\"LeastPrivilege\",\n\t\t\"false\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-task.vbs\",\n\t\t\"\" + cwd + \"\",\n\t\t\"\",\n\t\t\"999\",\n\t} {\n\t\tif !strings.Contains(taskXML, want) {\n\t\t\tt.Fatalf(\"task xml missing %q:\\n%s\", want, taskXML)\n\t\t}\n\t}\n\twatchdogXMLPath := filepath.Join(xmlDir, teamsServiceWindowsWatchdogTaskXMLName)\n\twatchdogData, err := os.ReadFile(watchdogXMLPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog task xml: %v\", err)\n\t}\n\twatchdogXML := string(watchdogData)\n\tfor _, want := range []string{\n\t\t\"Codex Helper Teams service watchdog\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"\",\n\t\t\"PT1M\",\n\t\t\"PT0S\",\n\t\t\"true\",\n\t\t\"wscript.exe\",\n\t\t\"//B //Nologo\",\n\t\t\"codex-helper-teams-watchdog-task.vbs\",\n\t} {\n\t\tif !strings.Contains(watchdogXML, want) {\n\t\t\tt.Fatalf(\"watchdog task xml missing %q:\\n%s\", want, watchdogXML)\n\t\t}\n\t}\n\tfor _, unexpected := range []string{\"powershell.exe\", \"-WindowStyle Hidden -Command\"} {\n\t\tif strings.Contains(taskXML, unexpected) || strings.Contains(watchdogXML, unexpected) {\n\t\t\tt.Fatalf(\"task XML should use no-console wscript launchers, found %q:\\nbridge=%s\\nwatchdog=%s\", unexpected, taskXML, watchdogXML)\n\t\t}\n\t}\n\tlauncherPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher powershell: %v\", err)\n\t}\n\tlauncherVBS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read bridge launcher vbs: %v\", err)\n\t}\n\twatchdogPS, err := os.ReadFile(filepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"))\n\tif err != nil {\n\t\tt.Fatalf(\"read watchdog launcher powershell: %v\", err)\n\t}\n\tfor _, want := range []string{\n\t\t\"codex-helper\\\\teams\",\n\t\t\"starting hidden Teams helper process\",\n\t\t\"Start-Process -FilePath\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t\t\"& '\" + exePath + \"' 'teams' 'run' '--owner-stale-after' '1m30s' '--auto-service=false' '--registry' '\" + registryPath + \"'\",\n\t\t\"$code = $LASTEXITCODE\",\n\t\t\"exit $code\",\n\t\t\"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\",\n\t} {\n\t\tif !strings.Contains(string(launcherPS), want) {\n\t\t\tt.Fatalf(\"bridge launcher powershell missing %q:\\n%s\", want, string(launcherPS))\n\t\t}\n\t}\n\tif !strings.Contains(string(watchdogPS), \"& '\"+exePath+\"' 'teams' 'service' 'watchdog' '--loop' '--interval' '10s' '--quiet'\") ||\n\t\t!strings.Contains(string(watchdogPS), \"$env:CODEX_HELPER_TEAMS_SERVICE = '1'\") {\n\t\tt.Fatalf(\"watchdog launcher powershell missing expected command/env:\\n%s\", string(watchdogPS))\n\t}\n\tif !strings.Contains(string(launcherVBS), \"shell.Run(cmd, 0, True)\") || !strings.Contains(string(launcherVBS), \"-WindowStyle Hidden -File\") {\n\t\tt.Fatalf(\"bridge launcher vbs should run PowerShell hidden:\\n%s\", string(launcherVBS))\n\t}\n\tif len(runner.calls) != 2 || runner.calls[0].name != \"powershell.exe\" || runner.calls[1].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"install should cleanup then register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tif !strings.Contains(cleanup, \"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\") ||\n\t\t!strings.Contains(cleanup, \"Stop-Process -Id $proc.ProcessId -Force\") ||\n\t\tstrings.Contains(cleanup, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should cleanup old bridge children before registering task, cleanup=%s\", cleanup)\n\t}\n\tjoined := strings.Join(runner.calls[1].args, \" \")\n\tif !strings.Contains(joined, \"Register-ScheduledTask\") {\n\t\tt.Fatalf(\"install should register task with powershell, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"install should register watchdog task too, call=%s\", joined)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"enable\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service enable: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"enable should call powershell scheduled task command, calls=%#v\", runner.calls)\n\t}\n\tif joined := strings.Join(runner.calls[0].args, \" \"); !strings.Contains(joined, teamsServiceWindowsTaskName) || !strings.Contains(joined, teamsServiceWindowsWatchdogTaskName) {\n\t\tt.Fatalf(\"enable should include main and watchdog tasks, call=%s\", joined)\n\t}\n\n\trunner.calls = nil\n\tcmd = newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetArgs([]string{\"uninstall\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service uninstall: %v\", err)\n\t}\n\tfor _, path := range []string{\n\t\txmlPath,\n\t\twatchdogXMLPath,\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-task.vbs\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.ps1\"),\n\t\tfilepath.Join(xmlDir, \"codex-helper-teams-watchdog-task.vbs\"),\n\t} {\n\t\tif _, err := os.Stat(path); !os.IsNotExist(err) {\n\t\t\tt.Fatalf(\"uninstall should remove %s, stat err=%v\", path, err)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceRepairCleansWindowsBridgeChildrenBeforeTaskRewrite(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work dir\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"windows-task\"),\n\t\trunner: runner,\n\t})\n\n\tif _, err := repairTeamsService(context.Background(), stringPtr(filepath.Join(tmp, \"teams registry.json\")), teamsServiceRepairOptions{Enable: true}); err != nil {\n\t\tt.Fatalf(\"repairTeamsService error: %v\", err)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"repair calls = %#v, want cleanup, register, enable\", runner.calls)\n\t}\n\tcleanup := strings.Join(runner.calls[0].args, \" \")\n\tfor _, want := range []string{\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"$expectedBridgeArgsForCleanup\",\n\t\t\"Get-CimInstance Win32_Process\",\n\t\t\"$proc.ProcessId -ne $PID\",\n\t\t\"Stop-Process -Id $proc.ProcessId -Force\",\n\t} {\n\t\tif !strings.Contains(cleanup, want) {\n\t\t\tt.Fatalf(\"repair cleanup command missing %q:\\n%s\", want, cleanup)\n\t\t}\n\t}\n\tif strings.Contains(cleanup, \"Register-ScheduledTask\") || strings.Contains(cleanup, \"$name -ieq 'codex-proxy.exe'\") || strings.Contains(cleanup, \"$name -ieq 'cxp.exe'\") {\n\t\tt.Fatalf(\"repair cleanup should use old task identity and fail closed:\\n%s\", cleanup)\n\t}\n\tallJoined := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, allJoined,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t\t\"Register-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsWatchdogTaskName),\n\t\t\"Enable-ScheduledTask -TaskName \"+powershellSingleQuote(teamsServiceWindowsTaskName),\n\t)\n}\n\nfunc TestTeamsServiceWindowsPowerShellPropagatesChildExitCodeCI(t *testing.T) {\n\tshell, err := exec.LookPath(\"pwsh\")\n\tif err != nil {\n\t\tshell, err = exec.LookPath(\"powershell.exe\")\n\t}\n\tif err != nil {\n\t\tt.Skip(\"PowerShell is not available\")\n\t}\n\n\ttmp := t.TempDir()\n\tchild := filepath.Join(tmp, \"exit-37\")\n\tif runtime.GOOS == \"windows\" {\n\t\tchild += \".cmd\"\n\t\tif err := os.WriteFile(child, []byte(\"@echo visible stdout\\r\\n@echo visible stderr 1>&2\\r\\n@exit /b 37\\r\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t} else {\n\t\tif err := os.WriteFile(child, []byte(\"#!/bin/sh\\necho visible stdout\\necho visible stderr >&2\\nexit 37\\n\"), 0o700); err != nil {\n\t\t\tt.Fatalf(\"write child command: %v\", err)\n\t\t}\n\t}\n\tscript := buildTeamsServiceWindowsPowerShell(teamsServiceSpec{\n\t\tExecutable: child,\n\t\tEnvironment: map[string]string{\"CODEX_HELPER_TEAMS_SERVICE\": \"1\"},\n\t}, []string{\"teams\", \"run\"})\n\tcmd := exec.Command(shell, \"-NoProfile\", \"-NonInteractive\", \"-ExecutionPolicy\", \"Bypass\", \"-Command\", script)\n\tout, err := cmd.CombinedOutput()\n\tvar exitErr *exec.ExitError\n\tif !errors.As(err, &exitErr) {\n\t\tt.Fatalf(\"PowerShell error = %T %v, want exit error\\noutput:\\n%s\", err, err, string(out))\n\t}\n\tif got := exitErr.ExitCode(); got != 37 {\n\t\tt.Fatalf(\"PowerShell exit code = %d, want child exit code 37\\noutput:\\n%s\", got, string(out))\n\t}\n\tif strings.TrimSpace(string(out)) != \"\" {\n\t\tt.Fatalf(\"PowerShell leaked child output instead of redirecting it:\\n%s\", string(out))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapShowsControlChatInForeground(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := filepath.Join(tmp, \"teams registry.json\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: cwd,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistry *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\tif gotRegistry == nil || *gotRegistry != registryPath {\n\t\t\t\tgot := \"\"\n\t\t\t\tif gotRegistry != nil {\n\t\t\t\t\tgot = *gotRegistry\n\t\t\t\t}\n\t\t\t\tt.Fatalf(\"registry path = %q, want %q\", got, registryPath)\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: openControl,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || !openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one true\", openValues)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"Teams service bootstrap ready: wsl-windows-task-scheduler\",\n\t\t\"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\",\n\t\t\"Open:\",\n\t\t\"https://teams.microsoft.com/l/chat/control\",\n\t\t\"Then send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"bootstrap should put the user's next step last:\\n%s\", got)\n\t}\n\tfor _, unwanted := range []string{\n\t\t\"Title: 🏠 Codex Control - workstation\",\n\t\t\"I also tried to open it automatically.\",\n\t} {\n\t\tif strings.Contains(got, unwanted) {\n\t\t\tt.Fatalf(\"bootstrap next-step output should not mix in secondary info %q:\\n%s\", unwanted, got)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithServiceCodexHomeEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\thome := filepath.Join(tmp, \"home\")\n\tprevUserHome := effectivePathsUserHomeDir\n\teffectivePathsUserHomeDir = func() (string, error) { return home, nil }\n\tt.Cleanup(func() { effectivePathsUserHomeDir = prevUserHome })\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\tvar gotService string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\tgotService = os.Getenv(\"CODEX_HELPER_TEAMS_SERVICE\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(home, \".codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif gotService != \"1\" {\n\t\tt.Fatalf(\"bootstrap control chat CODEX_HELPER_TEAMS_SERVICE = %q, want 1\", gotService)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithRelativeCodexDirEnv(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work\")\n\tt.Setenv(\"CODEX_HOME\", \"\")\n\tt.Setenv(\"CODEX_DIR\", \"relative-codex\")\n\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotHome string\n\tvar gotDir string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tgotHome = os.Getenv(\"CODEX_HOME\")\n\t\t\tgotDir = os.Getenv(\"CODEX_DIR\")\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantCodexHome := filepath.Join(cwd, \"relative-codex\")\n\tif gotHome != wantCodexHome || gotDir != wantCodexHome {\n\t\tt.Fatalf(\"bootstrap control chat Codex env = CODEX_HOME:%q CODEX_DIR:%q, want both %q\", gotHome, gotDir, wantCodexHome)\n\t}\n\tif os.Getenv(\"CODEX_HOME\") != \"\" || os.Getenv(\"CODEX_DIR\") != \"relative-codex\" {\n\t\tt.Fatalf(\"bootstrap control chat should restore caller Codex env, got CODEX_HOME:%q CODEX_DIR:%q\", os.Getenv(\"CODEX_HOME\"), os.Getenv(\"CODEX_DIR\"))\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPreparesControlChatWithResolvedRegistryPath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tcwd := filepath.Join(tmp, \"work dir\")\n\tregistryPath := \"teams registry.json\"\n\trunner := &recordingTeamsServiceRunner{}\n\tvar gotRegistry string\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"bin\", \"codex-proxy\"),\n\t\tcwd: cwd,\n\t\tunitDir: filepath.Join(tmp, \"systemd-user\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, gotRegistryPath *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\tif gotRegistryPath != nil {\n\t\t\t\tgotRegistry = *gotRegistryPath\n\t\t\t}\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\twantRegistry := filepath.Join(cwd, registryPath)\n\tif gotRegistry != wantRegistry {\n\t\tt.Fatalf(\"bootstrap control chat registry path = %q, want %q\", gotRegistry, wantRegistry)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSuppressesControlChatDiagnosticOutput(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar sawDiscardedDiagnostic bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\t_, _ = fmt.Fprintln(errOut, \"Teams Graph proxy: using http://127.0.0.1:44438\")\n\t\t\tsawDiscardedDiagnostic = true\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"🏠 Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t\tOpened: true,\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tvar errOut strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&errOut)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\\nstderr:\\n%s\", err, out.String(), errOut.String())\n\t}\n\tif !sawDiscardedDiagnostic {\n\t\tt.Fatal(\"bootstrap control chat hook was not called\")\n\t}\n\tif got := out.String() + errOut.String(); strings.Contains(got, \"Teams Graph proxy: using\") {\n\t\tt.Fatalf(\"bootstrap should suppress control chat diagnostics, got:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPrintsControlChatRecoveryAsFinalStep(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(ctx context.Context, root *rootOptions, gotRegistryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, errors.New(\"Teams Graph proxy is enabled but no unambiguous proxy profile is configured\")\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"BOOTSTRAP COMPLETE\",\n\t\t\"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\",\n\t\t\"Reason: Teams Graph proxy is enabled\",\n\t\t\"codex-proxy teams control\",\n\t\t\"Then open the printed link and send:\",\n\t\t\"help\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tcompleteIdx := strings.Index(got, \"BOOTSTRAP COMPLETE\")\n\tnextIdx := strings.Index(got, \"NEXT STEP: PRINT THE TEAMS CONTROL CHAT LINK\")\n\tif completeIdx < 0 || nextIdx < 0 || completeIdx > nextIdx {\n\t\tt.Fatalf(\"control chat recovery should be the final next-step block:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\ttaskDir := filepath.Join(tmp, \"tasks\")\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: taskDir,\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{\n\t\t\t\tURL: \"https://teams.microsoft.com/l/chat/control\",\n\t\t\t\tTopic: \"Codex Control - workstation\",\n\t\t\t\tChatID: \"control-chat\",\n\t\t\t}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Windows needs permission to create or repair the current-user Scheduled Task.\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\tfor _, want := range []string{\n\t\t\"Start-Process\",\n\t\t\"-Verb RunAs\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t\t\"Start-ScheduledTask\",\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t} {\n\t\tif !strings.Contains(elevated, want) {\n\t\t\tt.Fatalf(\"elevated command missing %q:\\n%s\", want, elevated)\n\t\t}\n\t}\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t)\n\tfor _, forbidden := range []string{\"RunLevel Highest\", \"HighestAvailable\", \"NT AUTHORITY\\\\SYSTEM\", \"-UserId 'SYSTEM'\", \"LogonType Password\"} {\n\t\tif strings.Contains(elevated, forbidden) {\n\t\t\tt.Fatalf(\"elevated command must stay current-user least-privilege, found %q:\\n%s\", forbidden, elevated)\n\t\t}\n\t}\n\tfor _, path := range []string{\n\t\tfilepath.Join(taskDir, teamsServiceWindowsTaskXMLName),\n\t\tfilepath.Join(taskDir, teamsServiceWindowsWatchdogTaskXMLName),\n\t} {\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"read task XML %s: %v\", path, err)\n\t\t}\n\t\txml := string(data)\n\t\tfor _, want := range []string{\n\t\t\t\"NVIDIA\\\\jason\",\n\t\t\t\"InteractiveToken\",\n\t\t\t\"LeastPrivilege\",\n\t\t} {\n\t\t\tif !strings.Contains(xml, want) {\n\t\t\t\tt.Fatalf(\"task XML %s missing %q:\\n%s\", path, want, xml)\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapWindowsAccessDeniedNoUACFailsWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, _ bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetErr(io.Discard)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\", \"--no-uac\"})\n\terr := cmd.Execute()\n\tif err == nil {\n\t\tt.Fatalf(\"bootstrap --no-uac error = nil, want access-denied failure\\noutput:\\n%s\", out.String())\n\t}\n\tif !strings.Contains(err.Error(), \"UAC is disabled by --no-uac\") {\n\t\tt.Fatalf(\"bootstrap --no-uac error = %v, want no-uac access-denied explanation\", err)\n\t}\n\tif strings.Contains(out.String(), \"NEXT STEP: TYPE yes TO CONTINUE\") || strings.Contains(out.String(), \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"bootstrap --no-uac must not prompt for UAC:\\n%s\", out.String())\n\t}\n\tif len(runner.calls) != 2 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup and failed register only\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapNoStartWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-start\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap --no-start error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Teams service bootstrap ready: windows-task-scheduler-uac-no-start\",\n\t\t\"Service was repaired but not started.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"bootstrap --no-start output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif strings.Contains(elevated, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap --no-start elevated repair must not start tasks:\\n%s\", elevated)\n\t}\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"yes\\n\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"NEXT STEP: TYPE yes TO CONTINUE\",\n\t\t\"Installed Teams service config:\",\n\t\t\"Service was not enabled or started automatically.\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t)\n\tfor _, forbidden := range []string{\"Enable-ScheduledTask\", \"Start-ScheduledTask\"} {\n\t\tif strings.Contains(elevated, forbidden) {\n\t\t\tt.Fatalf(\"install elevated repair must not run %s:\\n%s\", forbidden, elevated)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceInstallWindowsAccessDeniedYesUsesUACWithoutPrompt(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tgotOut := out.String()\n\tfor _, want := range []string{\n\t\t\"UAC prompt approved by --yes.\",\n\t\t\"Installed Teams service config:\",\n\t} {\n\t\tif !strings.Contains(gotOut, want) {\n\t\t\tt.Fatalf(\"install --yes output missing %q:\\n%s\", want, gotOut)\n\t\t}\n\t}\n\tif strings.Contains(gotOut, \"Type yes and press Enter\") {\n\t\tt.Fatalf(\"install --yes should not require stdin confirmation:\\n%s\", gotOut)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated install\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWindowsCleanupAccessDeniedUsesUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\t[]byte(\"Stop-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: filepath.Join(tmp, \"codex-proxy.exe\"),\n\t\tcwd: filepath.Join(tmp, \"work\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"\"))\n\tcmd.SetIn(strings.NewReader(\"\"))\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"install\", \"--yes\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"install --yes with cleanup access denied error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want failed cleanup, current-user lookup, and elevated install\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[2].args, \" \")\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t)\n\tif strings.Contains(elevated, \"Start-ScheduledTask\") || strings.Contains(elevated, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"install cleanup UAC repair must not enable or start tasks:\\n%s\", elevated)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapErrorSummaryKeepsFailuresReadable(t *testing.T) {\n\tlongPowerShellError := strings.Repeat(\"Register-ScheduledTask failed with noisy diagnostic details. \", 20)\n\tgot := teamsServiceBootstrapErrorSummary(errors.New(longPowerShellError))\n\tif strings.Contains(got, \"\\n\") {\n\t\tt.Fatalf(\"summary should be single-line, got %q\", got)\n\t}\n\tif len(got) > 323 {\n\t\tt.Fatalf(\"summary should be bounded, got len=%d text=%q\", len(got), got)\n\t}\n\tif !strings.HasSuffix(got, \"...\") {\n\t\tt.Fatalf(\"long summary should be truncated, got %q\", got)\n\t}\n\n\taccessDenied := teamsServiceBootstrapErrorSummary(errors.New(\"Register-ScheduledTask : Access is denied.\\nAt line:1 char:1\\nlarge command\"))\n\tif strings.Contains(accessDenied, \"Register-ScheduledTask\") || !strings.Contains(accessDenied, \"Windows denied permission\") {\n\t\tt.Fatalf(\"access denied should be summarized without raw PowerShell noise, got %q\", accessDenied)\n\t}\n}\n\nfunc TestTeamsServiceWindowsAccessDeniedErrorMatrix(t *testing.T) {\n\taccessDenied := []string{\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : Access is denied.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\",\n\t\t\"Register-ScheduledTask : Permission denied.\",\n\t\t\"UnauthorizedAccessException: Access to the path is denied.\",\n\t\t\"FullyQualifiedErrorId : AccessDenied\",\n\t\t\"Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)\",\n\t\t\"Register-ScheduledTask : 拒绝访问。\",\n\t\t\"Register-ScheduledTask : 存取被拒。\",\n\t\t\"Register-ScheduledTask : Zugriff verweigert.\",\n\t\t\"Register-ScheduledTask : Acceso denegado.\",\n\t\t\"Register-ScheduledTask : Accès refusé.\",\n\t\t\"Register-ScheduledTask : Accesso negato.\",\n\t}\n\tfor _, sample := range accessDenied {\n\t\tif !isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"access denied sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"powershell.exe -Command \\\"if (-not $actionMatches) { throw 'Teams WSL Scheduled Task action did not refresh; access is denied or task is protected' }\\\" failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Get-ScheduledTask : The system cannot find the file specified.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsAccessDeniedError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as access denied:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceWindowsScheduledTasksUnavailableErrorMatrix(t *testing.T) {\n\tunavailable := []string{\n\t\t\"Register-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"powershell.exe -Command Register-ScheduledTask failed: exit status 1\\nRegister-ScheduledTask : The term 'Register-ScheduledTask' is not recognized as the name of a cmdlet.\\nAt line:1 char:1\\n+ Register-ScheduledTask ...\\nCommandNotFoundException\",\n\t\t\"Get-ScheduledTask : The term 'Get-ScheduledTask' is not recognized as the name of a cmdlet\",\n\t\t\"The module 'ScheduledTasks' could not be loaded. For more information, run 'Import-Module ScheduledTasks'.\",\n\t\t\"Import-Module : The specified module 'ScheduledTasks' was not loaded because no valid module file was found\",\n\t}\n\tfor _, sample := range unavailable {\n\t\tif !isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"ScheduledTasks unavailable sample was not classified:\\n%s\", sample)\n\t\t}\n\t}\n\n\tunknown := []string{\n\t\t\"Register-ScheduledTask failed: exit status 1\",\n\t\t\"Register-ScheduledTask : The task XML contains a value which is incorrectly formatted.\",\n\t\t\"Register-ScheduledTask : Access is denied.\",\n\t}\n\tfor _, sample := range unknown {\n\t\tif isTeamsServiceWindowsScheduledTasksUnavailableError(errors.New(sample)) {\n\t\t\tt.Fatalf(\"unknown Windows Scheduled Task error was incorrectly classified as cmdlet unavailable:\\n%s\", sample)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapCanSkipAutomaticControlChatOpen(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\tvar openValues []bool\n\tregistryPath := \"/home/alice/teams registry.json\"\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t\tbootstrapControlChat: func(_ context.Context, _ *rootOptions, _ *string, openControl bool, _ io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\t\topenValues = append(openValues, openControl)\n\t\t\treturn teamsServiceBootstrapControlChatResult{URL: \"https://teams.microsoft.com/l/chat/control\"}, nil\n\t\t},\n\t})\n\n\tvar out strings.Builder\n\tcmd := newTeamsServiceCmd(&rootOptions{}, ®istryPath)\n\tcmd.SetOut(&out)\n\tcmd.SetArgs([]string{\"bootstrap\", \"--no-open-control\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"bootstrap error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif len(openValues) != 1 || openValues[0] {\n\t\tt.Fatalf(\"bootstrap control open values = %#v, want one false\", openValues)\n\t}\n\tif got := out.String(); !strings.Contains(got, \"NEXT STEP: OPEN THE TEAMS CONTROL CHAT\") || !strings.Contains(got, \"https://teams.microsoft.com/l/chat/control\") || strings.Contains(got, \"I also tried to open it automatically\") {\n\t\tt.Fatalf(\"bootstrap --no-open-control output is wrong:\\n%s\", got)\n\t}\n}\n\nfunc TestTeamsServiceBootstrapSchedulesPendingHelperActivationBeforeStartingOldWindowsEntry(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\", err)\n\t}\n\tif result.Mode != \"windows-pending-helper-activation\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation\", result)\n\t}\n\tif len(runner.calls) != 3 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, install/register, and enable only before activation\", runner.calls)\n\t}\n\tjoinedCalls := teamsServiceJoinedCalls(runner.calls)\n\trequireSubstringsInOrder(t, joinedCalls,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif !strings.Contains(joinedCalls, \"Register-ScheduledTask\") || !strings.Contains(joinedCalls, \"Enable-ScheduledTask\") {\n\t\tt.Fatalf(\"bootstrap should repair and enable tasks before pending activation, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\")\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"$want='0.1.0-rc.73'\",\n\t\t\".activation.json\",\n\t\t\"Move-Item -Force\",\n\t\t\"if (Test-DestVersion) { $ready=$true }\",\n\t\t\"Write-Status 'failed'\",\n\t\t\"Start-ScheduledTask\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceBootstrapPendingActivationWindowsAccessDeniedPromptsForUAC(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &scriptedTeamsServiceRunner{\n\t\toutputs: [][]byte{\n\t\t\tnil,\n\t\t\t[]byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\t[]byte(\"NVIDIA\\\\jason\\n\"),\n\t\t\tnil,\n\t\t},\n\t\terrs: []error{\n\t\t\tnil,\n\t\t\terrors.New(\"exit status 1\"),\n\t\t\tnil,\n\t\t\tnil,\n\t\t},\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tvar out strings.Builder\n\tresult, err := bootstrapTeamsService(context.Background(), nil, teamsServiceBootstrapOptions{\n\t\tAssumeYes: true,\n\t\tOut: &out,\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"bootstrapTeamsService error: %v\\noutput:\\n%s\", err, out.String())\n\t}\n\tif result.Mode != \"windows-pending-helper-activation-uac\" || !strings.HasSuffix(result.Path, teamsServiceWindowsTaskXMLName) {\n\t\tt.Fatalf(\"bootstrap result = %#v, want pending activation via UAC\", result)\n\t}\n\tif len(runner.calls) != 4 {\n\t\tt.Fatalf(\"PowerShell calls = %#v, want cleanup, failed register, current-user lookup, and elevated repair\", runner.calls)\n\t}\n\televated := strings.Join(runner.calls[3].args, \" \")\n\trequireSubstringsInOrder(t, elevated,\n\t\t\"$bridgeIdentityForCleanup = Get-CodexHelperBridgeTaskIdentity\",\n\t\t\"Register-ScheduledTask\",\n\t\t\"Enable-ScheduledTask\",\n\t)\n\tif strings.Contains(elevated, \"Start-ScheduledTask\") {\n\t\tt.Fatalf(\"pending activation elevated repair must not start old tasks:\\n%s\", elevated)\n\t}\n\tif detachedName == \"\" {\n\t\tt.Fatal(\"pending activation did not schedule detached PowerShell\")\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{\n\t\tpending,\n\t\texe,\n\t\tteamsServiceWindowsTaskName,\n\t\tteamsServiceWindowsWatchdogTaskName,\n\t\t\"Start-ScheduledTask\",\n\t} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\nname=%s\\nargs=%s\", want, detachedName, joined)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceStartPendingActivationUsesStableExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tif err := os.WriteFile(exe, []byte(\"stable\"), 0o644); err != nil {\n\t\tt.Fatalf(\"write exe: %v\", err)\n\t}\n\traw := exe + \".reload-backup-111-222\"\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want stable %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.70\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedArgs = append([]string{name}, args...)\n\t\treturn nil\n\t}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: raw,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: &recordingTeamsServiceRunner{},\n\t})\n\n\tscheduled, err := schedulePendingTeamsServiceActivationBeforeStart(context.Background(), io.Discard, \"start\")\n\tif err != nil {\n\t\tt.Fatalf(\"schedulePendingTeamsServiceActivationBeforeStart error: %v\", err)\n\t}\n\tif !scheduled {\n\t\tt.Fatal(\"expected pending activation to be scheduled\")\n\t}\n\tif len(detachedArgs) == 0 {\n\t\tt.Fatal(\"expected detached activation process\")\n\t}\n}\n\nfunc TestPendingHelperActivationAllowsTempSourceButRequiresStableDestination(t *testing.T) {\n\ttmp := t.TempDir()\n\tstableDest := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpendingSource := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tactivation, err := normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: stableDest,\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err != nil {\n\t\tt.Fatalf(\"pending source with stable destination should be allowed: %v\", err)\n\t}\n\tif activation.PendingPath != pendingSource || activation.InstallPath != stableDest {\n\t\tt.Fatalf(\"activation normalized unexpectedly: %#v\", activation)\n\t}\n\n\t_, err = normalizeTeamsPendingHelperActivation(teamsPendingHelperActivation{\n\t\tInstallPath: filepath.Join(tmp, \".nfs802014de01c482a9000004bf\"),\n\t\tPendingPath: pendingSource,\n\t\tVersion: \"0.1.0-rc.75\",\n\t})\n\tif err == nil || !strings.Contains(err.Error(), \"install path is not stable\") {\n\t\tt.Fatalf(\"transient destination should be rejected, got %v\", err)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsStalePendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tstale := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.73_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: stale, Version: \"0.1.0-rc.73\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(stale):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.73\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want stale pending replacement ignored\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"stale pending was probed %d time(s), want skipped before probe\", pendingProbeCount)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationAcceptsNewerPendingVersion(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif !ok {\n\t\tt.Fatal(\"discoverTeamsPendingHelperActivation ok = false, want newer pending activation\")\n\t}\n\tif activation.PendingPath != pending || activation.InstallPath != exe || activation.Version != \"0.1.0-rc.75\" {\n\t\tt.Fatalf(\"activation = %#v, want pending %q for %q\", activation, pending, exe)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalVersionIsNotComparable(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"dev\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry is not comparable\", activation)\n\t}\n}\n\nfunc TestDiscoverTeamsPendingHelperActivationRejectsPendingWhenFormalProbeFails(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t})\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{goos: \"windows\", exe: exe})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tvar pendingProbeCount int\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{}, errors.New(\"formal entry probe timed out\")\n\t\tcase filepath.Clean(pending):\n\t\t\tpendingProbeCount++\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\n\tactivation, ok, err := discoverTeamsPendingHelperActivation(context.Background(), exe, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"discoverTeamsPendingHelperActivation error: %v\", err)\n\t}\n\tif ok {\n\t\tt.Fatalf(\"activation = %#v, want pending ignored when formal entry probe fails\", activation)\n\t}\n\tif pendingProbeCount != 0 {\n\t\tt.Fatalf(\"pending was probed %d time(s), want skipped after formal probe failure\", pendingProbeCount)\n\t}\n}\n\nfunc TestTeamsServiceStartAndRestartActivatePendingWindowsHelperBeforeOldEntry(t *testing.T) {\n\tfor _, tc := range []struct {\n\t\tname string\n\t\targs []string\n\t\twant string\n\t}{\n\t\t{name: \"start\", args: []string{\"teams\", \"service\", \"start\"}, want: \"Scheduled Teams service start after activating pending helper v0.1.0-rc.74.\"},\n\t\t{name: \"restart\", args: []string{\"teams\", \"service\", \"restart\"}, want: \"Scheduled Teams service restart after activating pending helper v0.1.0-rc.74.\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\tlockCLITestHooks(t)\n\n\t\t\ttmp := t.TempDir()\n\t\t\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\t\t\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.74_windows_amd64.exe.123\")\n\t\t\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\t\t\tprevProbe := teamsUpdateProbeBinaryVersion\n\t\t\tprevDetached := teamsServiceStartDetached\n\t\t\tt.Cleanup(func() {\n\t\t\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\t\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\t\t\tteamsServiceStartDetached = prevDetached\n\t\t\t})\n\t\t\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\t\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t\t\t}\n\t\t\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.74\", ModTime: time.Now()}}, nil\n\t\t\t}\n\t\t\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\t\t\tswitch filepath.Clean(path) {\n\t\t\t\tcase filepath.Clean(exe):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\t\t\tcase filepath.Clean(pending):\n\t\t\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.74\"}, nil\n\t\t\t\tdefault:\n\t\t\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\t\t\treturn update.BinaryVersion{}, nil\n\t\t\t\t}\n\t\t\t}\n\t\t\tvar detachedName string\n\t\t\tvar detachedArgs []string\n\t\t\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\t\t\tdetachedName = name\n\t\t\t\tdetachedArgs = append([]string(nil), args...)\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: \"windows\",\n\t\t\t\texe: exe,\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\t\t\trunner: runner,\n\t\t\t})\n\n\t\t\tcmd := newRootCmd()\n\t\t\tcmd.SetArgs(tc.args)\n\t\t\tvar out bytes.Buffer\n\t\t\tcmd.SetOut(&out)\n\t\t\tcmd.SetErr(&out)\n\t\t\tif err := cmd.Execute(); err != nil {\n\t\t\t\tt.Fatalf(\"%s command error: %v\", tc.name, err)\n\t\t\t}\n\t\t\tif strings.TrimSpace(out.String()) != tc.want {\n\t\t\t\tt.Fatalf(\"%s output = %q, want %q\", tc.name, strings.TrimSpace(out.String()), tc.want)\n\t\t\t}\n\t\t\tif len(runner.calls) != 0 {\n\t\t\t\tt.Fatalf(\"%s should not start old scheduled task directly, calls=%#v\", tc.name, runner.calls)\n\t\t\t}\n\t\t\tif detachedName != \"powershell.exe\" {\n\t\t\t\tt.Fatalf(\"%s detached name = %q, want powershell.exe\", tc.name, detachedName)\n\t\t\t}\n\t\t\tjoined := strings.Join(detachedArgs, \" \")\n\t\t\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.74'\", \".activation.json\", \"Move-Item -Force\", \"Write-Status 'failed'\", \"Enable-ScheduledTask\", \"Start-ScheduledTask\"} {\n\t\t\t\tif !strings.Contains(joined, want) {\n\t\t\t\t\tt.Fatalf(\"%s activation command missing %q:\\n%s\", tc.name, want, joined)\n\t\t\t\t}\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceRestartForceRecoversBeforePendingWindowsActivation(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tisolateTeamsUserDirsForTest(t, tmp)\n\tst := seedRecoverableTeamsState(t)\n\towner, err := teamsstore.CurrentOwner(\"v-test\", \"s1\", \"turn:manual\", time.Now())\n\tif err != nil {\n\t\tt.Fatalf(\"CurrentOwner error: %v\", err)\n\t}\n\tif _, err := st.RecordOwnerHeartbeat(context.Background(), owner, time.Minute, time.Now()); err != nil {\n\t\tt.Fatalf(\"RecordOwnerHeartbeat error: %v\", err)\n\t}\n\texe := filepath.Join(tmp, \"codex-proxy.exe\")\n\tpending := filepath.Join(tmp, \".codex-proxy_0.1.0-rc.75_windows_amd64.exe.123\")\n\tprevFind := teamsUpdateFindPendingReplacementsForPlatform\n\tprevProbe := teamsUpdateProbeBinaryVersion\n\tprevDetached := teamsServiceStartDetached\n\tt.Cleanup(func() {\n\t\tteamsUpdateFindPendingReplacementsForPlatform = prevFind\n\t\tteamsUpdateProbeBinaryVersion = prevProbe\n\t\tteamsServiceStartDetached = prevDetached\n\t})\n\tteamsUpdateFindPendingReplacementsForPlatform = func(path string, goos string, goarch string) ([]update.PendingReplacement, error) {\n\t\tif filepath.Clean(path) != filepath.Clean(exe) || goos != \"windows\" {\n\t\t\tt.Fatalf(\"FindPendingReplacements path/goos = %q/%q, want %q/windows\", path, goos, exe)\n\t\t}\n\t\treturn []update.PendingReplacement{{Path: pending, Version: \"0.1.0-rc.75\", ModTime: time.Now()}}, nil\n\t}\n\tteamsUpdateProbeBinaryVersion = func(_ context.Context, path string, _ time.Duration) (update.BinaryVersion, error) {\n\t\tswitch filepath.Clean(path) {\n\t\tcase filepath.Clean(exe):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.68\"}, nil\n\t\tcase filepath.Clean(pending):\n\t\t\treturn update.BinaryVersion{Path: path, Version: \"0.1.0-rc.75\"}, nil\n\t\tdefault:\n\t\t\tt.Fatalf(\"unexpected probe path %q\", path)\n\t\t\treturn update.BinaryVersion{}, nil\n\t\t}\n\t}\n\tvar detachedName string\n\tvar detachedArgs []string\n\tteamsServiceStartDetached = func(_ context.Context, name string, args ...string) error {\n\t\tdetachedName = name\n\t\tdetachedArgs = append([]string(nil), args...)\n\t\treturn nil\n\t}\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"windows\",\n\t\texe: exe,\n\t\twindowsTaskDir: filepath.Join(tmp, \"tasks\"),\n\t\trunner: runner,\n\t})\n\n\tcmd := newRootCmd()\n\tcmd.SetArgs([]string{\"teams\", \"service\", \"restart\", \"--force\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tcmd.SetErr(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"restart --force command error: %v\\n%s\", err, out.String())\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Force recovering Teams state before service restart\",\n\t\t\"Recovered interrupted turns: 1\",\n\t\t\"Scheduled Teams service restart after activating pending helper v0.1.0-rc.75.\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"restart --force output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 0 {\n\t\tt.Fatalf(\"pending restart --force should not start old scheduled task directly, calls=%#v\", runner.calls)\n\t}\n\tif detachedName != \"powershell.exe\" {\n\t\tt.Fatalf(\"detached name = %q, want powershell.exe\", detachedName)\n\t}\n\tjoined := strings.Join(detachedArgs, \" \")\n\tfor _, want := range []string{pending, exe, \"$want='0.1.0-rc.75'\", \"Move-Item -Force\", \"Start-ScheduledTask\"} {\n\t\tif !strings.Contains(joined, want) {\n\t\t\tt.Fatalf(\"activation command missing %q:\\n%s\", want, joined)\n\t\t}\n\t}\n\tif _, ok, err := st.ReadOwner(context.Background()); err != nil {\n\t\tt.Fatalf(\"ReadOwner error: %v\", err)\n\t} else if ok {\n\t\tt.Fatal(\"owner should be cleared before pending activation\")\n\t}\n}\n\nfunc TestTeamsServiceOpenURLCommandMatrix(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttests := []struct {\n\t\tname string\n\t\tgoos string\n\t\tisWSL bool\n\t\twantExe string\n\t\twantArg string\n\t\twantErr string\n\t\twantCmdExe bool\n\t}{\n\t\t{name: \"windows\", goos: \"windows\", wantExe: \"rundll32.exe\", wantArg: \"url.dll,FileProtocolHandler\"},\n\t\t{name: \"macos\", goos: \"darwin\", wantExe: \"open\"},\n\t\t{name: \"linux desktop\", goos: \"linux\", wantExe: \"xdg-open\"},\n\t\t{name: \"wsl\", goos: \"linux\", isWSL: true, wantCmdExe: true, wantArg: \"start\"},\n\t\t{name: \"unsupported\", goos: \"freebsd\", wantErr: \"not supported\"},\n\t}\n\tfor _, tc := range tests {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: \"/tmp/codex-proxy\",\n\t\t\t\tcwd: \"/tmp\",\n\t\t\t\tisWSL: tc.isWSL,\n\t\t\t\trunner: &recordingTeamsServiceRunner{},\n\t\t\t\tunitDir: filepath.Join(t.TempDir(), \"systemd\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\twslDistro: \"Ubuntu\",\n\t\t\t})\n\t\t\tname, args, err := teamsServiceOpenURLCommand(\"https://teams.microsoft.com/l/chat/control\")\n\t\t\tif tc.wantErr != \"\" {\n\t\t\t\tif err == nil || !strings.Contains(err.Error(), tc.wantErr) {\n\t\t\t\t\tt.Fatalf(\"open URL error = %v, want containing %q\", err, tc.wantErr)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"open URL command error: %v\", err)\n\t\t\t}\n\t\t\tif tc.wantCmdExe {\n\t\t\t\tif filepath.Base(name) != \"cmd.exe\" {\n\t\t\t\t\tt.Fatalf(\"WSL opener = %q, want cmd.exe or absolute cmd.exe\", name)\n\t\t\t\t}\n\t\t\t} else if name != tc.wantExe {\n\t\t\t\tt.Fatalf(\"opener = %q, want %q\", name, tc.wantExe)\n\t\t\t}\n\t\t\tjoined := strings.Join(args, \" \")\n\t\t\tif !strings.Contains(joined, \"https://teams.microsoft.com/l/chat/control\") {\n\t\t\t\tt.Fatalf(\"opener args missing URL: %#v\", args)\n\t\t\t}\n\t\t\tif tc.wantArg != \"\" && !strings.Contains(joined, tc.wantArg) {\n\t\t\t\tt.Fatalf(\"opener args missing %q: %#v\", tc.wantArg, args)\n\t\t\t}\n\t\t})\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLHint(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service doctor: %v\", err)\n\t}\n\tgot := out.String()\n\tfor _, want := range []string{\n\t\t\"Teams service backend: wsl-windows-task-scheduler\",\n\t\t\"Codex Helper Teams Bridge (WSL Ubuntu alice default \",\n\t\t\"WSL: detected\",\n\t\t\"wsl.exe\",\n\t\t\"WSL supervisor readiness\",\n\t} {\n\t\tif !strings.Contains(got, want) {\n\t\t\tt.Fatalf(\"doctor output missing %q:\\n%s\", want, got)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-Command wsl.exe\") || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), \"Get-ScheduledTask\") {\n\t\tt.Fatalf(\"doctor should run a non-destructive WSL readiness probe, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Register-ScheduledTask\", \"Start-ScheduledTask\", \"Enable-ScheduledTask\", \"Disable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceRunPowerShellIncludesCombinedOutputOnFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/tmp/codex-proxy\",\n\t\tcwd: \"/tmp\",\n\t\tisWSL: true,\n\t\trunner: &recordingTeamsServiceRunner{\n\t\t\toutput: []byte(\"Register-ScheduledTask : Access is denied.\\n\"),\n\t\t\terr: errors.New(\"exit status 1\"),\n\t\t},\n\t})\n\n\t_, err := teamsServiceRunPowerShell(context.Background(), \"Register-ScheduledTask -TaskName 'Codex Helper Teams Bridge'\")\n\tif err == nil {\n\t\tt.Fatal(\"teamsServiceRunPowerShell error = nil, want failure\")\n\t}\n\tif !strings.Contains(err.Error(), \"exit status 1\") || !strings.Contains(err.Error(), \"Access is denied\") {\n\t\tt.Fatalf(\"PowerShell error did not preserve combined output:\\n%v\", err)\n\t}\n\tif !isTeamsServiceWindowsAccessDeniedError(err) {\n\t\tt.Fatalf(\"PowerShell access denied output should be classified as access denied:\\n%v\", err)\n\t}\n}\n\nfunc TestTeamsServiceDoctorReportsWSLReadinessFailure(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\trunner := &recordingTeamsServiceRunner{err: errors.New(\"powershell missing\")}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\tcwd: tmp,\n\t\tunitDir: filepath.Join(tmp, \"systemd\", \"user\"),\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(filepath.Join(tmp, \"registry.json\")))\n\tcmd.SetArgs([]string{\"doctor\"})\n\tvar out bytes.Buffer\n\tcmd.SetOut(&out)\n\terr := cmd.Execute()\n\tif err == nil || !strings.Contains(err.Error(), \"WSL Windows Scheduled Task readiness check failed\") {\n\t\tt.Fatalf(\"doctor error = %v, want WSL readiness failure\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"powershell.exe\" {\n\t\tt.Fatalf(\"doctor readiness calls=%#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceRunPowerShellCanUseExplicitExecutablePath(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\tisWSL: true,\n\t\trunner: runner,\n\t\tpowerShellExecutable: \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\",\n\t})\n\n\tif _, err := teamsServiceRunPowerShell(context.Background(), \"Get-Command wsl.exe\"); err != nil {\n\t\tt.Fatalf(\"teamsServiceRunPowerShell error: %v\", err)\n\t}\n\tif len(runner.calls) != 1 || runner.calls[0].name != \"/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe\" {\n\t\tt.Fatalf(\"powershell executable call = %#v\", runner.calls)\n\t}\n}\n\nfunc TestTeamsServiceInstallWritesWSLWindowsTask(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HOME\", filepath.Join(tmp, \"codex-home\"))\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work dir\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu-22.04\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\n\tcmd := newTeamsServiceCmd(&rootOptions{}, stringPtr(\"/home/alice/registry.json\"))\n\tcmd.SetArgs([]string{\"install\"})\n\tif err := cmd.Execute(); err != nil {\n\t\tt.Fatalf(\"execute service install: %v\", err)\n\t}\n\tfiles, err := filepath.Glob(filepath.Join(tmp, \"wsl-task\", \"codex-helper-teams-wsl-task-*.txt\"))\n\tif err != nil {\n\t\tt.Fatalf(\"glob WSL task config: %v\", err)\n\t}\n\tif len(files) != 1 {\n\t\tt.Fatalf(\"WSL task config files = %#v, want one\", files)\n\t}\n\tconfigPath := files[0]\n\tdata, err := os.ReadFile(configPath)\n\tif err != nil {\n\t\tt.Fatalf(\"read WSL task config: %v\", err)\n\t}\n\tconfig := string(data)\n\twantCWD := teamsServiceTestAbsPath(t, \"/home/alice/work dir\")\n\twantExe := teamsServiceTestAbsPath(t, \"/home/alice/bin/codex-proxy\")\n\twantRegistry := teamsServiceTestRegistryPath(wantCWD, \"/home/alice/registry.json\")\n\tfor _, want := range []string{\n\t\t\"TaskName=Codex Helper Teams Bridge (WSL Ubuntu-22.04 alice default \",\n\t\t\"Command=wsl.exe\",\n\t\t\"-d Ubuntu-22.04\",\n\t\t\"-u alice\",\n\t\t\"--cd \",\n\t\t\"--exec env\",\n\t\twantCWD,\n\t\t\"CODEX_HOME=\" + filepath.Join(tmp, \"codex-home\"),\n\t\twantExe + \" teams run --owner-stale-after 1m30s --auto-service=false --registry\",\n\t\twantRegistry,\n\t} {\n\t\tif !strings.Contains(config, want) {\n\t\t\tt.Fatalf(\"WSL config missing %q:\\n%s\", want, config)\n\t\t}\n\t}\n\tif len(runner.calls) != 1 {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tcall := strings.Join(runner.calls[0].args, \" \")\n\tif runner.calls[0].name != \"powershell.exe\" || !strings.Contains(call, \"Register-ScheduledTask\") || !strings.Contains(call, \"wsl.exe\") {\n\t\tt.Fatalf(\"install should register WSL scheduled task, calls=%#v\", runner.calls)\n\t}\n\tfor _, want := range []string{\n\t\t\"$expectedActionExecute = 'wscript.exe'\",\n\t\t\"$expectedActionArgument = '//B //Nologo\",\n\t\t\"WScript.Shell\",\n\t\t\"shell.Run(cmd, 0, True)\",\n\t\t\"WScript.Quit code\",\n\t\t\"-WindowStyle Hidden\",\n\t\t\"$wslArgumentLine\",\n\t\t\"Start-Process -FilePath ''wsl.exe''\",\n\t\t\"-RedirectStandardOutput $stdoutLog\",\n\t\t\"-RedirectStandardError $stderrLog\",\n\t} {\n\t\tif !strings.Contains(call, want) {\n\t\t\tt.Fatalf(\"WSL scheduled task should run wsl.exe through a hidden child process while preserving task lifetime; missing %q, calls=%#v\", want, runner.calls)\n\t\t}\n\t}\n\tif strings.Contains(call, \"& wsl.exe @wslArgs\") {\n\t\tt.Fatalf(\"WSL scheduled task should not launch wsl.exe directly in a visible console path, calls=%#v\", runner.calls)\n\t}\n\tif strings.Contains(call, \"RepetitionInterval\") || strings.Contains(call, \"Trigger @($logon, $watchdog)\") {\n\t\tt.Fatalf(\"WSL scheduled task should not use repeated Task Scheduler triggers, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"RestartCount 999\") {\n\t\tt.Fatalf(\"WSL task should use high restart count for background keepalive, calls=%#v\", runner.calls)\n\t}\n\tif !strings.Contains(call, \"Disable-ScheduledTask\") {\n\t\tt.Fatalf(\"install should leave WSL task disabled, calls=%#v\", runner.calls)\n\t}\n\tassertTeamsServiceCallsDoNotContain(t, runner.calls, \"Start-ScheduledTask\", \"Enable-ScheduledTask\")\n}\n\nfunc TestTeamsServiceWSLTaskNameSeparatesUsersAndProfiles(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"research/profile\")\n\trunner := &recordingTeamsServiceRunner{}\n\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\tgoos: \"linux\",\n\t\texe: \"/home/alice/bin/codex-proxy\",\n\t\tcwd: \"/home/alice/work\",\n\t\twindowsTaskDir: filepath.Join(tmp, \"wsl-task\"),\n\t\tisWSL: true,\n\t\twslDistro: \"Ubuntu/Dev\",\n\t\twslLinuxUser: \"alice\",\n\t\trunner: runner,\n\t})\n\taliceResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tteamsServiceWSLLinuxUserName = func() string { return \"bob\" }\n\tbobResearch := teamsServiceWSLWindowsTaskBackend{}.Name()\n\tt.Setenv(\"CODEX_HELPER_TEAMS_PROFILE\", \"default\")\n\tbobDefault := teamsServiceWSLWindowsTaskBackend{}.Name()\n\n\tif aliceResearch == bobResearch || bobResearch == bobDefault || aliceResearch == bobDefault {\n\t\tt.Fatalf(\"WSL task names should be isolated by Linux user and profile: alice=%q bob=%q default=%q\", aliceResearch, bobResearch, bobDefault)\n\t}\n\tfor _, name := range []string{aliceResearch, bobResearch, bobDefault} {\n\t\tif strings.ContainsAny(name, `\\/:*?\"<>|`) {\n\t\t\tt.Fatalf(\"WSL task name contains Windows-unsafe character: %q\", name)\n\t\t}\n\t}\n}\n\nfunc TestTeamsServiceActiveQueriesSupervisorWithoutGeneratedConfig(t *testing.T) {\n\tlockCLITestHooks(t)\n\n\ttmp := t.TempDir()\n\tfor _, tc := range []struct {\n\t\tname string\n\t\tgoos string\n\t\trunner string\n\t\twantName string\n\t}{\n\t\t{name: \"darwin\", goos: \"darwin\", runner: \"launchctl\", wantName: \"print\"},\n\t\t{name: \"windows\", goos: \"windows\", runner: \"powershell.exe\", wantName: \"Get-ScheduledTask\"},\n\t} {\n\t\tt.Run(tc.name, func(t *testing.T) {\n\t\t\trunner := &recordingTeamsServiceRunner{}\n\t\t\twithTeamsServiceTestHooks(t, teamsServiceTestHooks{\n\t\t\t\tgoos: tc.goos,\n\t\t\t\texe: filepath.Join(tmp, \"codex-proxy\"),\n\t\t\t\tcwd: tmp,\n\t\t\t\tlaunchAgentDir: filepath.Join(tmp, \"missing-launch-agents\"),\n\t\t\t\twindowsTaskDir: filepath.Join(tmp, \"missing-task-xml\"),\n\t\t\t\tuserID: \"501\",\n\t\t\t\trunner: runner,\n\t\t\t})\n\t\t\tactive, err := teamsServiceActive(context.Background())\n\t\t\tif err != nil {\n\t\t\t\tt.Fatalf(\"teamsServiceActive error: %v\", err)\n\t\t\t}\n\t\t\tif !active {\n\t\t\t\tt.Fatal(\"teamsServiceActive = false, want true from supervisor\")\n\t\t\t}\n\t\t\tif len(runner.calls) != 1 || runner.calls[0].name != tc.runner || !strings.Contains(strings.Join(runner.calls[0].args, \" \"), tc.wantName) {\n\t\t\t\tt.Fatalf(\"unexpected supervisor calls: %#v\", runner.calls)\n\t\t\t}\n\t\t})\n\t}\n}\n\ntype teamsServiceCommandCall struct {\n\tname string\n\targs []string\n}\n\ntype recordingTeamsServiceRunner struct {\n\tcalls []teamsServiceCommandCall\n\toutput []byte\n\terr error\n}\n\nfunc (r *recordingTeamsServiceRunner) Run(_ context.Context, name string, args ...string) ([]byte, error) {\n\tr.calls = append(r.calls, teamsServiceCommandCall{\n\t\tname: name,\n\t\targs: append([]string{}, args...),\n\t})\n\treturn append([]byte{}, r.output...), r.err\n}\n\nfunc teamsServiceCallSeen(calls []teamsServiceCommandCall, action string) bool {\n\tfor _, call := range calls {\n\t\tfor _, arg := range call.args {\n\t\t\tif arg == action {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc assertTeamsServiceCallsDoNotContain(t *testing.T, calls []teamsServiceCommandCall, forbidden ...string) {\n\tt.Helper()\n\tfor _, call := range calls {\n\t\tjoined := call.name + \" \" + strings.Join(call.args, \" \")\n\t\tfor _, value := range forbidden {\n\t\t\tif strings.Contains(joined, value) {\n\t\t\t\tt.Fatalf(\"service command contained forbidden %q: %#v\", value, calls)\n\t\t\t}\n\t\t}\n\t}\n}\n\ntype teamsServiceTestHooks struct {\n\tgoos string\n\texe string\n\targv0 string\n\tcwd string\n\tunitDir string\n\tlaunchAgentDir string\n\twindowsTaskDir string\n\tuserID string\n\tisWSL bool\n\twslDistro string\n\twslLinuxUser string\n\tpowerShellExecutable string\n\tsystemdUserAvailable *bool\n\tsystemdUserAvailableErr error\n\trunner teamsServiceCommandRunner\n\tbootstrapControlChat func(context.Context, *rootOptions, *string, bool, io.Writer) (teamsServiceBootstrapControlChatResult, error)\n\tlocalStartDetached func(context.Context, string, string, teamsServiceSpec) (int, error)\n\tlocalVerifyProcessIdentity func(int, string) error\n\tlocalVerifyChildIdentity func(int, teamsServiceSpec) error\n\tlocalProcessStartTime func(int) (string, error)\n\tlocalProcessArgs func(int) ([]string, error)\n\tlocalProcessEnvironment func(int) (map[string]string, error)\n\tlocalCheckChildHealth func(context.Context, *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error)\n\tlocalTerminateTarget func(*exec.Cmd, time.Duration) error\n\tlocalReadyTimeout time.Duration\n}\n\nfunc withTeamsServiceTestHooks(t *testing.T, hooks teamsServiceTestHooks) {\n\tt.Helper()\n\tt.Setenv(envTeamsCodexChild, \"\")\n\tt.Setenv(envTeamsCodexParentPID, \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_SERVICE_MODE\", \"\")\n\tt.Setenv(\"CODEX_HELPER_TEAMS_AUTO_SERVICE\", \"\")\n\tprevGOOS := teamsServiceGOOS\n\tprevExecutable := teamsServiceExecutable\n\tprevArgv0 := teamsServiceArgv0\n\tprevGetwd := teamsServiceGetwd\n\tprevSystemdUserDir := teamsServiceSystemdUserDir\n\tprevLaunchAgentDir := teamsServiceLaunchAgentDir\n\tprevWindowsTaskXMLDir := teamsServiceWindowsTaskXMLDir\n\tprevUserID := teamsServiceUserID\n\tprevIsWSL := teamsServiceIsWSL\n\tprevWSLDistroName := teamsServiceWSLDistroName\n\tprevWSLLinuxUserName := teamsServiceWSLLinuxUserName\n\tprevPowerShellExecutable := teamsServicePowerShellExecutable\n\tprevSystemctl := teamsServiceSystemctl\n\tprevSystemdUserAvailable := teamsServiceSystemdUserAvailable\n\tprevAuthPreflight := teamsServiceAuthPreflight\n\tprevBootstrapControlChat := teamsServiceBootstrapControlChat\n\tprevLocalSupervisorStartDetached := teamsServiceLocalSupervisorStartDetached\n\tprevLocalSupervisorReleaseProcess := teamsServiceLocalSupervisorReleaseProcess\n\tprevLocalSupervisorCheckChildHealth := teamsServiceLocalSupervisorCheckChildHealth\n\tprevLocalSupervisorTerminateTarget := teamsServiceLocalSupervisorTerminateTarget\n\tprevLocalSupervisorReadyTimeout := teamsServiceLocalSupervisorReadyTimeout\n\tprevLocalSupervisorVerifyProcessIdentity := teamsLocalSupervisorVerifyProcessIdentity\n\tprevLocalSupervisorVerifyChildIdentity := teamsLocalSupervisorVerifyChildIdentity\n\tprevLocalSupervisorProcessStartTime := teamsLocalSupervisorProcessStartTime\n\tprevLocalSupervisorProcessArgs := teamsLocalSupervisorProcessArgs\n\tprevLocalSupervisorProcessEnvironment := teamsLocalSupervisorProcessEnvironment\n\tprevListLocalProcesses := teamsServiceListLocalProcesses\n\tprevTerminateLocalProcess := teamsServiceTerminateLocalProcess\n\tprevLocalProcessGraceDelay := teamsServiceLocalProcessGraceDelay\n\tprevCurrentProcessID := teamsCurrentProcessID\n\tprevParentProcessID := teamsParentProcessID\n\tt.Setenv(\"CODEX_HELPER_TEAMS_LINUX_SERVICE_BACKEND\", \"\")\n\tteamsCurrentProcessID = os.Getpid\n\tteamsParentProcessID = os.Getppid\n\tteamsServiceGOOS = func() string { return hooks.goos }\n\tteamsServiceExecutable = func() (string, error) { return hooks.exe, nil }\n\tteamsServiceArgv0 = func() string { return hooks.argv0 }\n\tteamsServiceGetwd = func() (string, error) { return hooks.cwd, nil }\n\tteamsServiceSystemdUserDir = func() (string, error) { return hooks.unitDir, nil }\n\tteamsServiceLaunchAgentDir = func() (string, error) { return hooks.launchAgentDir, nil }\n\tteamsServiceWindowsTaskXMLDir = func() (string, error) { return hooks.windowsTaskDir, nil }\n\tteamsServiceUserID = func() string { return hooks.userID }\n\tteamsServiceIsWSL = func() bool { return hooks.isWSL }\n\tteamsServiceWSLDistroName = func() string { return hooks.wslDistro }\n\tteamsServiceWSLLinuxUserName = func() string { return hooks.wslLinuxUser }\n\tteamsServicePowerShellExecutable = func() string {\n\t\tif hooks.powerShellExecutable != \"\" {\n\t\t\treturn hooks.powerShellExecutable\n\t\t}\n\t\treturn \"powershell.exe\"\n\t}\n\tteamsServiceSystemctl = hooks.runner\n\tteamsServiceSystemdUserAvailable = func(context.Context) (bool, error) {\n\t\tif hooks.systemdUserAvailableErr != nil {\n\t\t\treturn false, hooks.systemdUserAvailableErr\n\t\t}\n\t\tif hooks.systemdUserAvailable != nil {\n\t\t\treturn *hooks.systemdUserAvailable, nil\n\t\t}\n\t\treturn true, nil\n\t}\n\tteamsServiceAuthPreflight = func() error { return nil }\n\tteamsServiceListLocalProcesses = func() ([]teamsServiceLocalProcess, error) { return nil, nil }\n\tteamsServiceTerminateLocalProcess = func(int, time.Duration) error { return nil }\n\tteamsServiceLocalProcessGraceDelay = 0\n\tteamsServiceBootstrapControlChat = func(ctx context.Context, root *rootOptions, registryPath *string, openControl bool, errOut io.Writer) (teamsServiceBootstrapControlChatResult, error) {\n\t\tif hooks.bootstrapControlChat != nil {\n\t\t\treturn hooks.bootstrapControlChat(ctx, root, registryPath, openControl, errOut)\n\t\t}\n\t\treturn teamsServiceBootstrapControlChatResult{}, nil\n\t}\n\tteamsServiceLocalSupervisorStartDetached = func(ctx context.Context, configPath string, logPath string, spec teamsServiceSpec) (int, error) {\n\t\tif hooks.localStartDetached != nil {\n\t\t\treturn hooks.localStartDetached(ctx, configPath, logPath, spec)\n\t\t}\n\t\treturn 4242, nil\n\t}\n\tteamsLocalSupervisorVerifyProcessIdentity = func(pid int, configPath string) error {\n\t\tif hooks.localVerifyProcessIdentity != nil {\n\t\t\treturn hooks.localVerifyProcessIdentity(pid, configPath)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorVerifyChildIdentity = func(pid int, spec teamsServiceSpec) error {\n\t\tif hooks.localVerifyChildIdentity != nil {\n\t\t\treturn hooks.localVerifyChildIdentity(pid, spec)\n\t\t}\n\t\treturn nil\n\t}\n\tteamsLocalSupervisorProcessStartTime = func(pid int) (string, error) {\n\t\tif hooks.localProcessStartTime != nil {\n\t\t\treturn hooks.localProcessStartTime(pid)\n\t\t}\n\t\treturn \"\", nil\n\t}\n\tteamsLocalSupervisorProcessArgs = func(pid int) ([]string, error) {\n\t\tif hooks.localProcessArgs != nil {\n\t\t\treturn hooks.localProcessArgs(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsLocalSupervisorProcessEnvironment = func(pid int) (map[string]string, error) {\n\t\tif hooks.localProcessEnvironment != nil {\n\t\t\treturn hooks.localProcessEnvironment(pid)\n\t\t}\n\t\treturn nil, nil\n\t}\n\tteamsServiceLocalSupervisorCheckChildHealth = func(ctx context.Context, state *teamsServiceWatchdogState) (teamsServiceWatchdogDecision, error) {\n\t\tif hooks.localCheckChildHealth != nil {\n\t\t\treturn hooks.localCheckChildHealth(ctx, state)\n\t\t}\n\t\treturn teamsServiceWatchdogDecision{Action: teamsServiceWatchdogActionNoop, Reason: \"test health check disabled\"}, nil\n\t}\n\tteamsServiceLocalSupervisorTerminateTarget = func(cmd *exec.Cmd, grace time.Duration) error {\n\t\tif hooks.localTerminateTarget != nil {\n\t\t\treturn hooks.localTerminateTarget(cmd, grace)\n\t\t}\n\t\treturn terminateTargetCommand(cmd, grace)\n\t}\n\tif hooks.localReadyTimeout > 0 {\n\t\tteamsServiceLocalSupervisorReadyTimeout = hooks.localReadyTimeout\n\t}\n\tt.Cleanup(func() {\n\t\tteamsServiceGOOS = prevGOOS\n\t\tteamsServiceExecutable = prevExecutable\n\t\tteamsServiceArgv0 = prevArgv0\n\t\tteamsServiceGetwd = prevGetwd\n\t\tteamsServiceSystemdUserDir = prevSystemdUserDir\n\t\tteamsServiceLaunchAgentDir = prevLaunchAgentDir\n\t\tteamsServiceWindowsTaskXMLDir = prevWindowsTaskXMLDir\n\t\tteamsServiceUserID = prevUserID\n\t\tteamsServiceIsWSL = prevIsWSL\n\t\tteamsServiceWSLDistroName = prevWSLDistroName\n\t\tteamsServiceWSLLinuxUserName = prevWSLLinuxUserName\n\t\tteamsServicePowerShellExecutable = prevPowerShellExecutable\n\t\tteamsServiceSystemctl = prevSystemctl\n\t\tteamsServiceSystemdUserAvailable = prevSystemdUserAvailable\n\t\tteamsServiceAuthPreflight = prevAuthPreflight\n\t\tteamsServiceBootstrapControlChat = prevBootstrapControlChat\n\t\tteamsServiceLocalSupervisorStartDetached = prevLocalSupervisorStartDetached\n\t\tteamsServiceLocalSupervisorReleaseProcess = prevLocalSupervisorReleaseProcess\n\t\tteamsServiceLocalSupervisorCheckChildHealth = prevLocalSupervisorCheckChildHealth\n\t\tteamsServiceLocalSupervisorTerminateTarget = prevLocalSupervisorTerminateTarget\n\t\tteamsServiceLocalSupervisorReadyTimeout = prevLocalSupervisorReadyTimeout\n\t\tteamsLocalSupervisorVerifyProcessIdentity = prevLocalSupervisorVerifyProcessIdentity\n\t\tteamsLocalSupervisorVerifyChildIdentity = prevLocalSupervisorVerifyChildIdentity\n\t\tteamsLocalSupervisorProcessStartTime = prevLocalSupervisorProcessStartTime\n\t\tteamsLocalSupervisorProcessArgs = prevLocalSupervisorProcessArgs\n\t\tteamsLocalSupervisorProcessEnvironment = prevLocalSupervisorProcessEnvironment\n\t\tteamsServiceListLocalProcesses = prevListLocalProcesses\n\t\tteamsServiceTerminateLocalProcess = prevTerminateLocalProcess\n\t\tteamsServiceLocalProcessGraceDelay = prevLocalProcessGraceDelay\n\t\tteamsCurrentProcessID = prevCurrentProcessID\n\t\tteamsParentProcessID = prevParentProcessID\n\t})\n}\n\nfunc stringPtr(s string) *string {\n\treturn &s\n}\n\nfunc testEnvValue(env []string, key string) (string, bool) {\n\tprefix := key + \"=\"\n\tvar out string\n\tok := false\n\tfor _, entry := range env {\n\t\tif strings.HasPrefix(entry, prefix) {\n\t\t\tout = strings.TrimPrefix(entry, prefix)\n\t\t\tok = true\n\t\t}\n\t}\n\treturn out, ok\n}\n"}}}}